From c527f7c1ea2f2642d627cca3baf47ffe08a526d1 Mon Sep 17 00:00:00 2001 From: Rifa Sania Date: Thu, 3 Apr 2025 17:19:19 +0700 Subject: [PATCH] api-documentation & query-testing --- package.json | 2 + src/config/swagger.ts | 37 ++++++ src/controllers/query-controller.ts | 18 +-- src/index.ts | 7 ++ src/operations/query.ts | 22 ++++ src/routes/apikey-routes.ts | 86 +++++++++++++ src/routes/auth-routes.ts | 134 ++++++++++++++++++++ src/routes/ddl-routes.ts | 183 ++++++++++++++++++++++++++++ src/routes/dml-routes.ts | 159 ++++++++++++++++++++++++ src/routes/query-routes.ts | 73 +++++++++++ src/routes/schema-routes.ts | 70 +++++++++++ src/routes/user-routes.ts | 55 +++++++++ src/tests/query.test.ts | 163 +++++++++++++++++++++++++ yarn.lock | 142 ++++++++++++++++++++- 14 files changed, 1134 insertions(+), 17 deletions(-) create mode 100644 src/config/swagger.ts create mode 100644 src/operations/query.ts create mode 100644 src/tests/query.test.ts diff --git a/package.json b/package.json index cba1f54..7760523 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,8 @@ "pg-format": "^1.0.4", "pg-hstore": "^2.3.4", "sequelize": "^6.37.5", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1", "uuid": "^11.0.5" }, "devDependencies": { diff --git a/src/config/swagger.ts b/src/config/swagger.ts new file mode 100644 index 0000000..4faa9e1 --- /dev/null +++ b/src/config/swagger.ts @@ -0,0 +1,37 @@ +import { SwaggerOptions } from 'swagger-jsdoc'; + +const swaggerDefinition = { + openapi: '3.0.0', + info: { + title: 'Backend as a Service API', + version: '1.0.0', + description: 'API Documentation for BaaS Project', + }, + servers: [ + { + url: 'http://localhost:3000/api', + description: 'Local server', + }, + ], + components: { + securitySchemes: { + BearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + ApiKeyAuth: { + type: 'apiKey', + in: 'header', + name: 'x-api-key', + }, + }, + }, +}; + +const options: SwaggerOptions = { + swaggerDefinition, + apis: ['src/routes/*.ts'], +}; + +export default options; diff --git a/src/controllers/query-controller.ts b/src/controllers/query-controller.ts index 11892c0..78bcfe6 100644 --- a/src/controllers/query-controller.ts +++ b/src/controllers/query-controller.ts @@ -1,26 +1,12 @@ import { Request, Response } from 'express'; -import { sequelize } from '../config/database'; -import { QueryTypes } from 'sequelize'; import { errorResponse, successResponse } from '../utils/response'; +import { QueryExecutor } from '../operations/query'; export const executeQuery = async (req: Request, res: Response) => { try { const { query } = req.body; - if (!query || typeof query !== 'string') { - return errorResponse(res, 'Query is required and must be a string', 400); - } - - const forbiddenPatterns = [ - /DROP\s+TABLE/i, - /ALTER\s+/i, - /DELETE\s+FROM\s+[^\s]+(\s*;|$)/i, - ]; - if (forbiddenPatterns.some((pattern) => pattern.test(query))) { - return errorResponse(res, 'Query contains forbidden operations', 403); - } - - const result = await sequelize.query(query, { type: QueryTypes.RAW }); + const result = await QueryExecutor.execute(query); return successResponse(res, result, 'Query executed successfully'); } catch (error) { console.error(error); diff --git a/src/index.ts b/src/index.ts index dbcc677..b4edf5b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,8 @@ import express, { Request, Response } from 'express'; import dotenv from 'dotenv'; +import swaggerJsDoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +import swaggerOptions from './config/swagger'; import userRoutes from './routes/user-routes'; import authRoutes from './routes/auth-routes'; import ddlRoutes from './routes/ddl-routes'; @@ -37,10 +40,14 @@ apiRouter.use('/apikey', apikeyRoutes); app.use('/api', apiRouter); +const swaggerDocs = swaggerJsDoc(swaggerOptions); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs)); + app.get('/health', (req: Request, res: Response) => { res.send('Hello, TypeScript with Express!'); }); app.listen(PORT, () => { console.log(`Server is running on http://localhost:${PORT}`); + console.log(`Swagger Docs available at http://localhost:${PORT}/api-docs`); }); diff --git a/src/operations/query.ts b/src/operations/query.ts new file mode 100644 index 0000000..1cca0b9 --- /dev/null +++ b/src/operations/query.ts @@ -0,0 +1,22 @@ +import { sequelize } from '../config/database'; +import { QueryTypes } from 'sequelize'; + +export class QueryExecutor { + static async execute(query: string) { + if (!query || typeof query !== 'string') { + throw new Error('Query is required and must be a string'); + } + + const forbiddenPatterns = [ + /DROP\s+TABLE/i, + /ALTER\s+/i, + /DELETE\s+FROM\s+[^\s]+(\s*;|$)/i, + ]; + if (forbiddenPatterns.some((pattern) => pattern.test(query))) { + throw new Error('Query contains forbidden operations'); + } + + const result = await sequelize.query(query, { type: QueryTypes.RAW }); + return result; + } +} diff --git a/src/routes/apikey-routes.ts b/src/routes/apikey-routes.ts index ca1fd27..1c86805 100644 --- a/src/routes/apikey-routes.ts +++ b/src/routes/apikey-routes.ts @@ -4,7 +4,93 @@ import { getApiKey, regenerateApiKey } from '../controllers/apikey-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: API Key + * description: API for managing user API key + */ + +/** + * @swagger + * /apikey: + * get: + * summary: Get API key + * tags: [API Key] + * security: + * - BearerAuth: [] + * responses: + * 200: + * description: Successfully retrieved API key + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: User details retrieved + * apikey: + * type: string + * example: 'example-api-key' + * 401: + * description: Unauthorized (Invalid or missing token) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + */ router.get('/', authMiddleware, getApiKey); + +/** + * @swagger + * /apikey: + * put: + * summary: Regenerate API key + * tags: [API Key] + * security: + * - BearerAuth: [] + * responses: + * 200: + * description: Successfully regenerated API key + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: User details retrieved + * apikey: + * type: string + * example: 'new-example-api-key' + * 401: + * description: Unauthorized (Invalid or missing token) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + */ router.put('/', authMiddleware, regenerateApiKey); export default router; diff --git a/src/routes/auth-routes.ts b/src/routes/auth-routes.ts index 91556f5..efcfc39 100644 --- a/src/routes/auth-routes.ts +++ b/src/routes/auth-routes.ts @@ -3,7 +3,141 @@ import { registerUser, loginUser } from '../controllers/user-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: Authentication + * description: API for user authentication + */ + +/** + * @swagger + * /auth/register: + * post: + * summary: Register a new user + * tags: [Authentication] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * - email + * - password + * properties: + * name: + * type: string + * example: Full Name + * email: + * type: string + * format: email + * example: user@example.com + * password: + * type: string + * format: password + * example: password123 + * responses: + * 200: + * description: User registered successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: User registered successfully + * data: + * type: object + * properties: + * id: + * type: string + * example: "123e4567-e89b-12d3-a456-426614174000" + * 400: + * description: Bad request (Missing fields or email already registered) + * content: + * application/json: + * examples: + * missing_fields: + * summary: Missing required fields + * value: + * success: false + * message: "Name, email, and password are required" + * email_registered: + * summary: Email already exists + * value: + * success: false + * message: "Email is already registered" + * 500: + * description: Internal server error + */ router.post('/register', registerUser); + +/** + * @swagger + * /auth/login: + * post: + * summary: Login a user + * tags: [Authentication] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - email + * - password + * properties: + * email: + * type: string + * format: email + * example: user@example.com + * password: + * type: string + * format: password + * example: password123 + * responses: + * 200: + * description: Login successful, returns a JWT token + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: Login successful + * data: + * type: object + * properties: + * token: + * type: string + * example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." + * 401: + * description: Unauthorized + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + * 500: + * description: Internal server error + */ router.post('/login', loginUser); export default router; diff --git a/src/routes/ddl-routes.ts b/src/routes/ddl-routes.ts index aebf72c..6061ba2 100644 --- a/src/routes/ddl-routes.ts +++ b/src/routes/ddl-routes.ts @@ -4,6 +4,189 @@ import { migrate } from '../controllers/ddl-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: DDL Operations + * description: API for performing DDL (Data Definition Language) operations + */ + +/** + * @swagger + * /migrate: + * post: + * summary: Execute DDL operations + * tags: [DDL Operations] + * security: + * - ApiKeyAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * operations: + * type: array + * items: + * type: object + * properties: + * operation: + * type: string + * enum: [Create, Alter, Drop] + * resource: + * type: string + * enum: [Table, Column] + * migration: + * type: object + * properties: + * name: + * type: string + * table: + * type: string + * column: + * type: object + * properties: + * type: + * type: string + * definition: + * type: object + * properties: + * textType: + * type: string + * nullable: + * type: boolean + * unique: + * type: boolean + * default: + * type: string + * primary: + * type: boolean + * from: + * type: string + * to: + * type: string + * primaryKey: + * type: string + * example: + * operations: + * - operation: "Create" + * resource: "Table" + * migration: + * name: "test" + * primaryKey: "UUID" + * - operation: "Create" + * resource: "Column" + * migration: + * name: "name" + * table: "test" + * column: + * type: "text" + * definition: + * textType: "text" + * default: null + * unique: false + * nullable: true + * - operation: "Create" + * resource: "Column" + * migration: + * name: "description" + * table: "test" + * column: + * type: "text" + * definition: + * textType: "text" + * default: null + * unique: false + * nullable: true + * - operation: "Create" + * resource: "Column" + * migration: + * name: "created_at" + * table: "test" + * column: + * type: "timestamp" + * definition: + * default: "now()" + * nullable: true + * - operation: "Create" + * resource: "Column" + * migration: + * name: "updated_at" + * table: "test" + * column: + * type: "timestamp" + * definition: + * default: "now()" + * nullable: true + * - operation: "Alter" + * resource: "Column" + * migration: + * from: "description" + * to: "external_id" + * table: "test" + * column: + * definition: + * unique: true + * default: null + * nullable: false + * - operation: "Alter" + * resource: "Table" + * migration: + * from: "test" + * to: "new_test" + * - operation: "Drop" + * resource: "Column" + * migration: + * table: "new_test" + * column: "external_id" + * - operation: "Drop" + * resource: "Table" + * migration: + * name: "new_test" + * responses: + * 200: + * description: DDL operations completed successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: DDL operations completed successfully + * 400: + * description: Invalid payload structure + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Invalid payload structure + * 401: + * description: Unauthorized (Invalid or missing API key) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + * 500: + * description: Internal server error + */ router.post('/', apiKeyMiddleware, migrate); export default router; diff --git a/src/routes/dml-routes.ts b/src/routes/dml-routes.ts index c8eeaca..94acee4 100644 --- a/src/routes/dml-routes.ts +++ b/src/routes/dml-routes.ts @@ -4,6 +4,165 @@ import { execute } from '../controllers/dml-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: DML Operations + * description: API for performing DML (Data Manipulation Language) operations + */ + +/** + * @swagger + * /execute: + * post: + * summary: Execute DML operations + * tags: [DML Operations] + * security: + * - ApiKeyAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * operations: + * type: array + * items: + * type: object + * properties: + * operation: + * type: string + * enum: [Select, Insert, Update, Delete] + * instruction: + * type: object + * properties: + * table: + * type: string + * name: + * type: string + * data: + * type: object + * set: + * type: object + * condition: + * type: object + * orderBy: + * type: object + * additionalProperties: + * type: string + * enum: [ASC, DESC] + * limit: + * type: integer + * offset: + * type: integer + * params: + * type: object + * example: + * operations: + * - operation: "Insert" + * instruction: + * table: "test_dml" + * name: "data" + * data: + * external_id: "admin1" + * email: "admin1@admin.com" + * - operation: "Insert" + * instruction: + * table: "test_dml" + * name: "data" + * data: + * external_id: "admin4" + * email: "admin4@admin.com" + * - operation: "Update" + * instruction: + * table: "test_dml" + * name: "data" + * condition: + * "$and": + * - external_id: + * "$eq": "admin1" + * set: + * external_id: "admin1" + * - operation: "Delete" + * instruction: + * table: "test_dml" + * name: "data" + * condition: + * "$and": + * - external_id: + * "$eq": "admin1" + * params: {} + * - operation: "Select" + * instruction: + * name: "data" + * orderBy: + * created_at: "ASC" + * condition: {} + * limit: 26 + * offset: 0 + * params: {} + * table: "test_dml" + * - operation: "Select" + * instruction: + * name: "data" + * orderBy: + * created_at: "ASC" + * condition: + * "$or": + * - "$or": + * - email: + * "$eq": "{{name}}" + * - external_id: + * "$eq": "user1" + * limit: 26 + * offset: 0 + * params: + * name: "admin@admin.com" + * table: "test_dml" + * responses: + * 200: + * description: DML operations executed successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: DML operations completed successfully + * 400: + * description: Invalid payload structure + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Invalid payload structure + * 401: + * description: Unauthorized (Invalid or missing API key) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + * 500: + * description: Internal server error + */ router.post('/', apiKeyMiddleware, execute); export default router; diff --git a/src/routes/query-routes.ts b/src/routes/query-routes.ts index d3d36da..6753e2d 100644 --- a/src/routes/query-routes.ts +++ b/src/routes/query-routes.ts @@ -4,6 +4,79 @@ import { executeQuery } from '../controllers/query-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: Query Operations + * description: API for executing custom queries + */ + +/** + * @swagger + * /query: + * post: + * summary: Execute a custom SQL query + * tags: [Query Operations] + * security: + * - ApiKeyAuth: [] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * query: + * type: string + * description: The SQL query string to execute + * example: + * query: "SELECT * FROM test_dml WHERE email = '{{email}}'" + * responses: + * 200: + * description: Query executed successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: Query executed successfully + * result: + * type: array + * items: + * type: object + * additionalProperties: true + * 401: + * description: Unauthorized (Invalid or missing API key) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Failed to execute query + */ router.post('/', apiKeyMiddleware, executeQuery); export default router; diff --git a/src/routes/schema-routes.ts b/src/routes/schema-routes.ts index 5f4182a..3afd218 100644 --- a/src/routes/schema-routes.ts +++ b/src/routes/schema-routes.ts @@ -4,6 +4,76 @@ import { schema } from '../controllers/schema-controller'; const router = Router(); +/** + * @swagger + * tags: + * name: Schema Operations + * description: API for retrieving schema information of tables and columns + */ + +/** + * @swagger + * /schemas: + * get: + * summary: Get schema information for all tables + * tags: [Schema Operations] + * security: + * - ApiKeyAuth: [] + * responses: + * 200: + * description: Schema information retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: Schemas retrieved successfully + * tables: + * type: array + * items: + * type: object + * properties: + * table_name: + * type: string + * example: "users" + * columns: + * type: array + * items: + * type: object + * properties: + * column_name: + * type: string + * example: "id" + * data_type: + * type: string + * example: "integer" + * is_nullable: + * type: boolean + * example: false + * default: + * type: string + * example: null + * 401: + * description: Unauthorized (Invalid or missing API key) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + * 500: + * description: Internal server error + */ router.get('/', apiKeyMiddleware, schema); export default router; diff --git a/src/routes/user-routes.ts b/src/routes/user-routes.ts index dfb753d..9f01071 100644 --- a/src/routes/user-routes.ts +++ b/src/routes/user-routes.ts @@ -4,6 +4,61 @@ import { authMiddleware } from '../middlewares/auth-middleware'; const router = Router(); +/** + * @swagger + * tags: + * name: Users + * description: API for managing user accounts + */ + +/** + * @swagger + * /users/user: + * get: + * summary: Get user details + * tags: [Users] + * security: + * - BearerAuth: [] + * responses: + * 200: + * description: User details retrieved successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: User details retrieved + * data: + * type: object + * properties: + * id: + * type: string + * example: "123e4567-e89b-12d3-a456-426614174000" + * name: + * type: string + * example: "Full Name" + * email: + * type: string + * example: "user@example.com" + * 401: + * description: Unauthorized (Invalid or missing token) + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * example: Unauthorized + */ router.get('/user', authMiddleware, getUserDetails); export default router; diff --git a/src/tests/query.test.ts b/src/tests/query.test.ts new file mode 100644 index 0000000..543ee9c --- /dev/null +++ b/src/tests/query.test.ts @@ -0,0 +1,163 @@ +import { sequelize } from '../config/database'; +import { Transaction } from 'sequelize'; +import { DDLExecutor } from '../operations/migrate'; +import { DDLOperations } from '../types/ddl'; +import { QueryExecutor } from '../operations/query'; +import MetadataTableRepository from '../repositories/metadata-table-repository'; + +let transaction: Transaction; + +beforeAll(async () => { + await sequelize.authenticate(); + + const ddlTransaction = await sequelize.transaction(); + + try { + const ddlPayload: DDLOperations[] = [ + { + operation: 'Create', + resource: 'Table', + migration: { name: 'test_query', primaryKey: 'UUID' }, + }, + { + operation: 'Create', + resource: 'Column', + migration: { + name: 'email', + table: 'test_query', + column: { + type: 'text', + definition: { + textType: 'text', + default: null, + unique: false, + nullable: true, + }, + }, + }, + }, + { + operation: 'Create', + resource: 'Column', + migration: { + name: 'external_id', + table: 'test_query', + column: { + type: 'text', + definition: { + textType: 'text', + default: null, + unique: false, + nullable: true, + }, + }, + }, + }, + { + operation: 'Create', + resource: 'Column', + migration: { + name: 'created_at', + table: 'test_query', + column: { + type: 'timestamp', + definition: { default: 'now()', unique: false, nullable: false }, + }, + }, + }, + { + operation: 'Create', + resource: 'Column', + migration: { + name: 'count', + table: 'test_query', + column: { + type: 'integer', + definition: { default: 0, unique: false, nullable: true }, + }, + }, + }, + ]; + + await DDLExecutor.execute(ddlPayload, ddlTransaction); + await ddlTransaction.commit(); + } catch (error) { + await ddlTransaction.rollback(); + throw error; + } +}); + +afterAll(async () => { + await MetadataTableRepository.delete({ table_name: 'test_query' }); + await sequelize.query('DROP TABLE IF EXISTS test_query CASCADE;'); + await sequelize.close(); +}); + +describe('SQL Query Execution', () => { + beforeEach(async () => { + transaction = await sequelize.transaction(); + }); + + afterEach(async () => { + await transaction.rollback(); + }); + + test('Execute a sequence of queries (INSERT, SELECT, UPDATE, DELETE) successfully', async () => { + const query = ` + INSERT INTO test_query (email, external_id, count) VALUES ('user1@example.com', 'user1', 5); + INSERT INTO test_query (email, external_id, count) VALUES ('user2@example.com', 'user2', 10); + UPDATE test_query SET count = 15 WHERE email = 'user1@example.com'; + DELETE FROM test_query WHERE email = 'user2@example.com'; + SELECT * FROM test_query; + `; + + const result = await QueryExecutor.execute(query); + console.log('Final query result:', result); + + const selectResult = result[0]; + + expect(Array.isArray(selectResult)).toBe(true); + expect(selectResult.length).toBe(1); + expect(selectResult[0]).toMatchObject({ + email: 'user1@example.com', + external_id: 'user1', + count: 15, + }); + }); + + test('Fail to insert NULL into a NOT NULL column', async () => { + const query = `INSERT INTO test_query (email, external_id, count) VALUES ('user1@example.com', NULL, 20);`; + await expect(QueryExecutor.execute(query)).rejects.toThrow(); + }); + + test('Fail due to invalid SQL syntax', async () => { + const query = `SELEC * FROM test_query;`; + await expect(QueryExecutor.execute(query)).rejects.toThrow(); + }); + + test('Fail: DROP TABLE should be rejected', async () => { + const query = `DROP TABLE test_query;`; + await expect(QueryExecutor.execute(query)).rejects.toThrow( + /Query contains forbidden operations/i, + ); + }); + + test('Fail: ALTER TABLE DROP COLUMN should be rejected', async () => { + const query = `ALTER TABLE test_query DROP COLUMN email;`; + await expect(QueryExecutor.execute(query)).rejects.toThrow( + /Query contains forbidden operations/i, + ); + }); + + test('Fail: DELETE without WHERE should be rejected', async () => { + const query = `DELETE FROM test_query;`; + await expect(QueryExecutor.execute(query)).rejects.toThrow( + /Query contains forbidden operations/i, + ); + }); + + test('Fail: Invalid SQL syntax should be rejected', async () => { + const query = `THIS IS NOT A VALID SQL STATEMENT`; + await expect(QueryExecutor.execute(query)).rejects.toThrow(/syntax error/i); + }); +}); diff --git a/yarn.lock b/yarn.lock index a801ded..d71f120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,6 +10,38 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" +"@apidevtools/json-schema-ref-parser@^9.0.6": + version "9.1.2" + resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz#8ff5386b365d4c9faa7c8b566ff16a46a577d9b8" + integrity sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg== + dependencies: + "@jsdevtools/ono" "^7.1.3" + "@types/json-schema" "^7.0.6" + call-me-maybe "^1.0.1" + js-yaml "^4.1.0" + +"@apidevtools/openapi-schemas@^2.0.4": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz#9fa08017fb59d80538812f03fc7cac5992caaa17" + integrity sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ== + +"@apidevtools/swagger-methods@^3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz#b789a362e055b0340d04712eafe7027ddc1ac267" + integrity sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg== + +"@apidevtools/swagger-parser@10.0.3": + version "10.0.3" + resolved "https://registry.yarnpkg.com/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz#32057ae99487872c4dd96b314a1ab4b95d89eaf5" + integrity sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g== + dependencies: + "@apidevtools/json-schema-ref-parser" "^9.0.6" + "@apidevtools/openapi-schemas" "^2.0.4" + "@apidevtools/swagger-methods" "^3.0.2" + "@jsdevtools/ono" "^7.1.3" + call-me-maybe "^1.0.1" + z-schema "^5.0.1" + "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" @@ -636,6 +668,11 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jsdevtools/ono@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" + integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -672,6 +709,11 @@ resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -852,7 +894,7 @@ expect "^29.0.0" pretty-format "^29.0.0" -"@types/json-schema@^7.0.15": +"@types/json-schema@^7.0.15", "@types/json-schema@^7.0.6": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -1326,6 +1368,11 @@ call-bound@^1.0.2: call-bind-apply-helpers "^1.0.1" get-intrinsic "^1.2.6" +call-me-maybe@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" + integrity sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ== + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -1460,6 +1507,11 @@ colorette@^2.0.20: resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== +commander@6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.0.tgz#b990bfb8ac030aedc6d11bc04d1488ffef56db75" + integrity sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q== + commander@^10.0.0: version "10.0.1" resolved "https://registry.yarnpkg.com/commander/-/commander-10.0.1.tgz#881ee46b4f77d1c1dccc5823433aa39b022cbe06" @@ -1599,6 +1651,13 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +doctrine@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" + integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== + dependencies: + esutils "^2.0.2" + dotenv@^16.4.7: version "16.4.7" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.7.tgz#0e20c5b82950140aa99be360a8a5f52335f53c26" @@ -2219,6 +2278,18 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^10.4.2: version "10.4.5" resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" @@ -3085,6 +3156,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -3095,6 +3171,11 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" @@ -3125,6 +3206,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash.mergewith@^4.6.2: + version "4.6.2" + resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" + integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== + lodash.once@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" @@ -4174,6 +4260,39 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +swagger-jsdoc@^6.2.8: + version "6.2.8" + resolved "https://registry.yarnpkg.com/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz#6d33d9fb07ff4a7c1564379c52c08989ec7d0256" + integrity sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ== + dependencies: + commander "6.2.0" + doctrine "3.0.0" + glob "7.1.6" + lodash.mergewith "^4.6.2" + swagger-parser "^10.0.3" + yaml "2.0.0-1" + +swagger-parser@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/swagger-parser/-/swagger-parser-10.0.3.tgz#04cb01c18c3ac192b41161c77f81e79309135d03" + integrity sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg== + dependencies: + "@apidevtools/swagger-parser" "10.0.3" + +swagger-ui-dist@>=5.0.0: + version "5.20.3" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.20.3.tgz#af13c31cd2c3189191f0ec3de5d5a1ff55f24bab" + integrity sha512-BVMj5gR1y3SN7kMRsgLnyN2zi21ITfIoJjKZOeCrcz6BRx5wNeHMr3efyRY1dGmPxPuaCDNj2PcwTzQaDfrbCQ== + dependencies: + "@scarf/scarf" "=1.4.0" + +swagger-ui-express@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz#fb8c1b781d2793a6bd2f8a205a3f4bd6fa020dd8" + integrity sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA== + dependencies: + swagger-ui-dist ">=5.0.0" + synckit@^0.9.1: version "0.9.2" resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62" @@ -4390,6 +4509,11 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validator@^13.7.0: + version "13.15.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.15.0.tgz#2dc7ce057e7513a55585109eec29b2c8e8c1aefd" + integrity sha512-36B2ryl4+oL5QxZ3AzD0t5SsMNGvTtQHpjgFO5tbNxfXbMFkY822ktCDe1MnlqV3301QQI9SLHDNJokDI+Z9pA== + validator@^13.9.0: version "13.12.0" resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" @@ -4490,6 +4614,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@2.0.0-1: + version "2.0.0-1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.0.0-1.tgz#8c3029b3ee2028306d5bcf396980623115ff8d18" + integrity sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ== + yaml@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98" @@ -4540,3 +4669,14 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +z-schema@^5.0.1: + version "5.0.6" + resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.6.tgz#46d6a687b15e4a4369e18d6cb1c7b8618fc256c5" + integrity sha512-+XR1GhnWklYdfr8YaZv/iu+vY+ux7V5DS5zH1DQf6bO5ufrt/5cgNhVO5qyhsjFXvsqQb/f08DWE9b6uPscyAg== + dependencies: + lodash.get "^4.4.2" + lodash.isequal "^4.5.0" + validator "^13.7.0" + optionalDependencies: + commander "^10.0.0"