diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..dcbd3ed --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "endOfLine": "auto", + "printWidth": 80, + "tabWidth": 2, + "useTabs": false +} diff --git a/eslint.config.js b/eslint.config.js index c63bec4..118dfca 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,21 +1,43 @@ import globals from "globals"; import pluginJs from "@eslint/js"; +import pluginPrettier from "eslint-plugin-prettier"; export default [ { languageOptions: { globals: { ...globals.browser, - process: true, // Adiciona 'process' como uma variável global + process: true, }, }, - ignores: ['**/*.test.js', '**/*.test.mjs', '**/__tests__/'], // Ignorar arquivos de teste + ignores: ['**/*.test.js', '**/*.test.mjs', '**/__tests__/'], }, pluginJs.configs.recommended, { rules: { - 'no-unused-vars': ['warn', { varsIgnorePattern: '^_' }], // Ignorar variáveis que começam com '_' - 'no-undef': 'off', // Desativar a regra 'no-undef' + 'no-unused-vars': ['warn', { varsIgnorePattern: '^_' }], + 'no-undef': 'off', + 'no-warning-comments': [ + 'warn', + { terms: ['TODO', 'FIXME'], location: 'anywhere' } + ], + 'prettier/prettier': [ + 'error', + { + endOfLine: 'auto', + semi: true, + singleQuote: true, + printWidth: 80, + tabWidth: 2, + useTabs: false, + bracketSpacing: true, + trailingComma: 'es5', + arrowParens: 'avoid', + }, + ], + }, + plugins: { + prettier: pluginPrettier, }, }, ]; diff --git a/package-lock.json b/package-lock.json index b2b1dc3..edeb627 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,8 +22,11 @@ "@vitest/coverage-istanbul": "^2.1.1", "@vitest/coverage-v8": "^2.1.1", "eslint": "^9.11.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "globals": "^15.9.0", "nodemon": "^3.1.7", + "prettier": "^3.3.3", "supertest": "^7.0.0", "vitest": "^2.1.1" }, @@ -1188,6 +1191,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.22.5", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.22.5.tgz", @@ -2421,6 +2436,48 @@ } } }, + "node_modules/eslint-config-prettier": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", + "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "dev": true, + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0", + "synckit": "^0.9.1" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-plugin-prettier" + }, + "peerDependencies": { + "@types/eslint": ">=8.0.0", + "eslint": ">=8.0.0", + "eslint-config-prettier": "*", + "prettier": ">=3.0.0" + }, + "peerDependenciesMeta": { + "@types/eslint": { + "optional": true + }, + "eslint-config-prettier": { + "optional": true + } + } + }, "node_modules/eslint-scope": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.1.0.tgz", @@ -2614,6 +2671,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4063,6 +4126,33 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4682,6 +4772,22 @@ "express": ">=4.0.0 || >=5.0.0-beta" } }, + "node_modules/synckit": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -4828,6 +4934,12 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", + "dev": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index c553e3a..6395d4c 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "module": "src/main.js", "type": "module", "scripts": { + "format": "prettier --write src/**/*.{js,mjs,json}", "lint": "eslint src --fix", "test": "vitest", "coverage": "vitest run --coverage", @@ -29,8 +30,11 @@ "@vitest/coverage-istanbul": "^2.1.1", "@vitest/coverage-v8": "^2.1.1", "eslint": "^9.11.1", + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1", "globals": "^15.9.0", "nodemon": "^3.1.7", + "prettier": "^3.3.3", "supertest": "^7.0.0", "vitest": "^2.1.1" }, diff --git a/src/controller/ai/ aiController.test.js b/src/controller/ai/ aiController.test.js index 4715db9..51ce33f 100644 --- a/src/controller/ai/ aiController.test.js +++ b/src/controller/ai/ aiController.test.js @@ -12,54 +12,50 @@ vi.mock('../../service/ai/ai.service.js', () => ({ describe('aiController', () => { it('deve retornar uma resposta com status 200 e a mensagem correta', async () => { - const mockResponse = { content: JSON.stringify({ message: 'Sucesso!', statusCode: 200, - status: 'ok' - }) + status: 'ok', + }), }; - const aiService = (await import('../../service/ai/ai.service.js')).default; aiService.mockResolvedValue(mockResponse); const res = await request(app) .post('/ai') - .send({ }) + .send({}) .set('Authorization', 'Bearer token'); expect(res.status).toBe(200); expect(res.body).toEqual({ status: 'ok', - message: 'Sucesso!' + message: 'Sucesso!', }); }); it('deve retornar um erro se o serviço falhar', async () => { - const errorResponse = { content: JSON.stringify({ message: 'Erro!', statusCode: 500, - status: 'error' - }) + status: 'error', + }), }; - const aiService = (await import('../../service/ai/ai.service.js')).default; aiService.mockResolvedValue(errorResponse); const res = await request(app) .post('/ai') - .send({ }) + .send({}) .set('Authorization', 'Bearer token'); expect(res.status).toBe(500); expect(res.body).toEqual({ status: 'error', - message: 'Erro!' + message: 'Erro!', }); }); }); diff --git a/src/controller/ai/ai.controller.js b/src/controller/ai/ai.controller.js index b51e669..4aeb846 100644 --- a/src/controller/ai/ai.controller.js +++ b/src/controller/ai/ai.controller.js @@ -6,6 +6,6 @@ const aiController = async (req, res) => { const { message, statusCode, status } = JSON.parse(response.content); res.status(statusCode).json({ status, message }); -} +}; -export default aiController; \ No newline at end of file +export default aiController; diff --git a/src/database/config/mongoose.config.js b/src/database/config/mongoose.config.js index 1ea3142..5506c6f 100644 --- a/src/database/config/mongoose.config.js +++ b/src/database/config/mongoose.config.js @@ -1,6 +1,6 @@ import mongoose from 'mongoose'; -const startMongoose = async () => { +const startMongoose = async () => { try { await mongoose.connect(process.env.MONGODB_URI); console.log('Connected to MongoDB'); @@ -8,6 +8,6 @@ const startMongoose = async () => { console.error('Error connecting to MongoDB: ', error); process.exit(1); } -} +}; -export default startMongoose; \ No newline at end of file +export default startMongoose; diff --git a/src/database/model/index.js b/src/database/model/index.js index 980a869..6cee32a 100644 --- a/src/database/model/index.js +++ b/src/database/model/index.js @@ -1,5 +1,5 @@ -import User from './user.model.js' -import Product from './product.model.js' -import Order from './order.model.js' +import User from './user.model.js'; +import Product from './product.model.js'; +import Order from './order.model.js'; -export { User, Product, Order } \ No newline at end of file +export { User, Product, Order }; diff --git a/src/database/repository/ai.repository.js b/src/database/repository/ai.repository.js index bb30813..7efee19 100644 --- a/src/database/repository/ai.repository.js +++ b/src/database/repository/ai.repository.js @@ -20,7 +20,7 @@ const aiRepository = async ({ model, query, transaction }) => { return await models[model].deleteOne(query).exec(); }, }; - + if (methods[transaction] && models[model]) { return await methods[transaction](); } else { @@ -32,8 +32,8 @@ const aiRepository = async ({ model, query, transaction }) => { } } } catch (err) { - return { status: 'error', message: err.message}; + return { status: 'error', message: err.message }; } }; -export default aiRepository; \ No newline at end of file +export default aiRepository; diff --git a/src/database/repository/ai.repository.test.js b/src/database/repository/ai.repository.test.js index 30c4f7d..f966277 100644 --- a/src/database/repository/ai.repository.test.js +++ b/src/database/repository/ai.repository.test.js @@ -7,7 +7,7 @@ vi.mock('../model/index.js'); // Mockando o módulo de modelos describe('aiRepository', () => { const modelName = 'User'; // Substitua pelo nome do seu modelo const query = { name: 'Test' }; - + afterEach(() => { vi.clearAllMocks(); // Limpa mocks após cada teste }); @@ -15,9 +15,13 @@ describe('aiRepository', () => { it('should create a document', async () => { const mockDocument = { save: vi.fn().mockResolvedValue(query) }; models[modelName].mockImplementation(() => mockDocument); - - const result = await aiRepository({ model: modelName, query, transaction: 'create' }); - + + const result = await aiRepository({ + model: modelName, + query, + transaction: 'create', + }); + expect(models[modelName]).toHaveBeenCalledWith(query); expect(mockDocument.save).toHaveBeenCalled(); expect(result).toEqual(query); @@ -26,9 +30,13 @@ describe('aiRepository', () => { it('should find documents', async () => { const mockFind = { exec: vi.fn().mockResolvedValue([query]) }; models[modelName].find.mockReturnValue(mockFind); - - const result = await aiRepository({ model: modelName, query, transaction: 'find' }); - + + const result = await aiRepository({ + model: modelName, + query, + transaction: 'find', + }); + expect(mockFind.exec).toHaveBeenCalled(); expect(result).toEqual([query]); }); @@ -36,9 +44,13 @@ describe('aiRepository', () => { it('should find a single document', async () => { const mockFindOne = { exec: vi.fn().mockResolvedValue(query) }; models[modelName].findOne.mockReturnValue(mockFindOne); - - const result = await aiRepository({ model: modelName, query, transaction: 'findOne' }); - + + const result = await aiRepository({ + model: modelName, + query, + transaction: 'findOne', + }); + expect(mockFindOne.exec).toHaveBeenCalled(); expect(result).toEqual(query); }); @@ -46,33 +58,60 @@ describe('aiRepository', () => { it('should update a document', async () => { const mockUpdateOne = { exec: vi.fn().mockResolvedValue({ nModified: 1 }) }; models[modelName].updateOne.mockReturnValue(mockUpdateOne); - - const updateQuery = { filter: { _id: '1' }, update: { name: 'Updated Test' } }; - const result = await aiRepository({ model: modelName, query: updateQuery, transaction: 'update' }); - - expect(models[modelName].updateOne).toHaveBeenCalledWith(updateQuery.filter, updateQuery.update); + + const updateQuery = { + filter: { _id: '1' }, + update: { name: 'Updated Test' }, + }; + const result = await aiRepository({ + model: modelName, + query: updateQuery, + transaction: 'update', + }); + + expect(models[modelName].updateOne).toHaveBeenCalledWith( + updateQuery.filter, + updateQuery.update + ); expect(mockUpdateOne.exec).toHaveBeenCalled(); expect(result).toEqual({ nModified: 1 }); }); it('should delete a document', async () => { - const mockDeleteOne = { exec: vi.fn().mockResolvedValue({ deletedCount: 1 }) }; + const mockDeleteOne = { + exec: vi.fn().mockResolvedValue({ deletedCount: 1 }), + }; models[modelName].deleteOne.mockReturnValue(mockDeleteOne); - - const result = await aiRepository({ model: modelName, query, transaction: 'delete' }); - + + const result = await aiRepository({ + model: modelName, + query, + transaction: 'delete', + }); + expect(models[modelName].deleteOne).toHaveBeenCalledWith(query); expect(mockDeleteOne.exec).toHaveBeenCalled(); expect(result).toEqual({ deletedCount: 1 }); }); it('should throw an error for invalid transaction type', async () => { - const result = await aiRepository({ model: modelName, query, transaction: 'invalid' }); - expect(result).toEqual({ "message": "Invalid transaction type", "status": "error" }); + const result = await aiRepository({ + model: modelName, + query, + transaction: 'invalid', + }); + expect(result).toEqual({ + message: 'Invalid transaction type', + status: 'error', + }); }); - + it('should throw an error for invalid model', async () => { - const result = await aiRepository({ model: 'InvalidModel', query, transaction: 'find' }); - expect(result).toEqual({ "message": "Invalid model", "status": "error" }); + const result = await aiRepository({ + model: 'InvalidModel', + query, + transaction: 'find', + }); + expect(result).toEqual({ message: 'Invalid model', status: 'error' }); }); }); diff --git a/src/database/schema/index.js b/src/database/schema/index.js index 2c2daeb..d6ed9f4 100644 --- a/src/database/schema/index.js +++ b/src/database/schema/index.js @@ -2,4 +2,4 @@ import OrderSchema from './order.schema.js'; import ProductSchema from './product.schema.js'; import UserSchema from './user.schema.js'; -export { OrderSchema, ProductSchema, UserSchema }; \ No newline at end of file +export { OrderSchema, ProductSchema, UserSchema }; diff --git a/src/database/schema/order.schema.js b/src/database/schema/order.schema.js index 511d4e5..7ff03d0 100644 --- a/src/database/schema/order.schema.js +++ b/src/database/schema/order.schema.js @@ -6,18 +6,20 @@ const OrderSchema = new mongoose.Schema({ ref: 'User', required: true, }, - products: [{ - productId: { - type: mongoose.Schema.Types.ObjectId, - ref: 'Product', - required: true, + products: [ + { + productId: { + type: mongoose.Schema.Types.ObjectId, + ref: 'Product', + required: true, + }, + quantity: { + type: Number, + required: true, + min: 1, + }, }, - quantity: { - type: Number, - required: true, - min: 1, - }, - }], + ], totalAmount: { type: Number, required: true, diff --git a/src/database/schema/user.schema.js b/src/database/schema/user.schema.js index 7113b81..40514ff 100644 --- a/src/database/schema/user.schema.js +++ b/src/database/schema/user.schema.js @@ -28,4 +28,4 @@ const UserSchema = new mongoose.Schema({ }, }); -export default UserSchema; \ No newline at end of file +export default UserSchema; diff --git a/src/helper/gpt/config/gpt.config.js b/src/helper/gpt/config/gpt.config.js index 322b446..3a68c18 100644 --- a/src/helper/gpt/config/gpt.config.js +++ b/src/helper/gpt/config/gpt.config.js @@ -1,21 +1,21 @@ import OpenAI from 'openai'; -import * as tools from '../tool/index.js' +import * as tools from '../tool/index.js'; export default async function gpt({ completion }) { try { const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); - + const response = await openai.chat.completions.create({ model: process.env.OPENAI_MODEL, messages: completion, // temperature: process.env.OPENAI_TEMPERATURE, - tools: tools.default - }) + tools: tools.default, + }); if (response.choices[0].message.tool_calls) { - console.log(response.choices[0].message.tool_calls) + console.log(response.choices[0].message.tool_calls); } - return response.choices[0].message + return response.choices[0].message; } catch (err) { - console.log(err.message) - return err.message + console.log(err.message); + return err.message; } -} \ No newline at end of file +} diff --git a/src/helper/gpt/config/gpt.config.test.js b/src/helper/gpt/config/gpt.config.test.js index f3abef2..56c6e84 100644 --- a/src/helper/gpt/config/gpt.config.test.js +++ b/src/helper/gpt/config/gpt.config.test.js @@ -54,7 +54,9 @@ describe('gpt', () => { await gpt({ completion: mockCompletion }); - expect(consoleLogSpy).toHaveBeenCalledWith(mockResponse.choices[0].message.tool_calls); + expect(consoleLogSpy).toHaveBeenCalledWith( + mockResponse.choices[0].message.tool_calls + ); consoleLogSpy.mockRestore(); }); diff --git a/src/helper/gpt/tool/database/aiRepository.tool.js b/src/helper/gpt/tool/database/aiRepository.tool.js index 07ac85e..68bb2de 100644 --- a/src/helper/gpt/tool/database/aiRepository.tool.js +++ b/src/helper/gpt/tool/database/aiRepository.tool.js @@ -1,41 +1,46 @@ const aiRepositoryTool = { - "type": "function", - "function": { - "name": "ai_repository", - "description": "Perform generic database operations using Mongoose. Specify the model, query, and transaction type.", - "parameters": { - "type": "object", - "properties": { - "model": { - "type": "string", - "description": "The name of the Mongoose model to operate on." + type: 'function', + function: { + name: 'ai_repository', + description: + 'Perform generic database operations using Mongoose. Specify the model, query, and transaction type.', + parameters: { + type: 'object', + properties: { + model: { + type: 'string', + description: 'The name of the Mongoose model to operate on.', }, - "query": { - "type": "object", - "description": "The query parameters used for the operation. Structure depends on the transaction type, Required for update operations. Include subpropeties query.filter and query.update", - "properties": { - "filter": { - "type": "object", - "description": "Filter criteria for find, findOne, update, and delete operations." + query: { + type: 'object', + description: + 'The query parameters used for the operation. Structure depends on the transaction type, Required for update operations. Include subpropeties query.filter and query.update', + properties: { + filter: { + type: 'object', + description: + 'Filter criteria for find, findOne, update, and delete operations.', + }, + update: { + type: 'object', + description: + 'Data to update. Required for update operations. Include subpropeties query.filter and query.update', }, - "update": { - "type": "object", - "description": "Data to update. Required for update operations. Include subpropeties query.filter and query.update" - } }, - "required": ["filter"], - "additionalProperties": true + required: ['filter'], + additionalProperties: true, + }, + transaction: { + type: 'string', + description: + "The type of operation to perform: 'find', 'findOne', 'create', 'update', or 'delete'.", + enum: ['find', 'findOne', 'create', 'update', 'delete'], }, - "transaction": { - "type": "string", - "description": "The type of operation to perform: 'find', 'findOne', 'create', 'update', or 'delete'.", - "enum": ["find", "findOne", "create", "update", "delete"] - } }, - "required": ["model", "query", "transaction"], - "additionalProperties": false - } - } -} + required: ['model', 'query', 'transaction'], + additionalProperties: false, + }, + }, +}; export default aiRepositoryTool; diff --git a/src/helper/gpt/tool/database/getSchemas.tool.js b/src/helper/gpt/tool/database/getSchemas.tool.js index 596ddd2..62bdc6a 100644 --- a/src/helper/gpt/tool/database/getSchemas.tool.js +++ b/src/helper/gpt/tool/database/getSchemas.tool.js @@ -1,70 +1,71 @@ const getSchemasTool = { - "type": "function", - "function": { - "name": "get_schemas", - "description": "Retrieve available schemas and their properties for database operations.", - "parameters": { - "type": "object", - "properties": { - "schemas": { - "type": "array", - "description": "List of available schemas.", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "Name of the schema." + type: 'function', + function: { + name: 'get_schemas', + description: + 'Retrieve available schemas and their properties for database operations.', + parameters: { + type: 'object', + properties: { + schemas: { + type: 'array', + description: 'List of available schemas.', + items: { + type: 'object', + properties: { + name: { + type: 'string', + description: 'Name of the schema.', }, - "description": { - "type": "string", - "description": "Description of the schema." + description: { + type: 'string', + description: 'Description of the schema.', }, - "model": { - "type": "string", - "description": "Name of the corresponding model." + model: { + type: 'string', + description: 'Name of the corresponding model.', }, - "properties": { - "type": "object", - "description": "Properties of the schema.", - "additionalProperties": { - "type": "object", - "properties": { - "required": { - "type": "boolean", - "description": "Indicates if the property is required." + properties: { + type: 'object', + description: 'Properties of the schema.', + additionalProperties: { + type: 'object', + properties: { + required: { + type: 'boolean', + description: 'Indicates if the property is required.', }, - "unique": { - "type": "boolean", - "description": "Indicates if the property must be unique." + unique: { + type: 'boolean', + description: 'Indicates if the property must be unique.', }, - "default": { - "type": "string", - "description": "Default value for the property." + default: { + type: 'string', + description: 'Default value for the property.', }, - "type": { - "type": "string", - "description": "Data type of the property." + type: { + type: 'string', + description: 'Data type of the property.', }, - "description": { - "type": "string", - "description": "Description of the property." + description: { + type: 'string', + description: 'Description of the property.', }, - "enum": { - "type": "array", - "description": "Possible values for the property." - } - } - } - } - } - } - } + enum: { + type: 'array', + description: 'Possible values for the property.', + }, + }, + }, + }, + }, + }, + }, }, - "required": [], - "additionalProperties": false - } - } -} + required: [], + additionalProperties: false, + }, + }, +}; export default getSchemasTool; diff --git a/src/helper/gpt/tool/database/mongoRepository.tool.js b/src/helper/gpt/tool/database/mongoRepository.tool.js index 83b0a73..cde64ae 100644 --- a/src/helper/gpt/tool/database/mongoRepository.tool.js +++ b/src/helper/gpt/tool/database/mongoRepository.tool.js @@ -1,30 +1,32 @@ const mongoRepositoryTool = { - "type": "function", - "function": { - "name": "mongo_repository", - "description": "Perform generic database operations using MongoDB. Specify the collection, operation type, and query parameters.", - "parameters": { - "type": "object", - "properties": { - "collection": { - "type": "string", - "description": "The name of the MongoDB collection to operate on.", - "enum": ["Order", "Product", "User"] + type: 'function', + function: { + name: 'mongo_repository', + description: + 'Perform generic database operations using MongoDB. Specify the collection, operation type, and query parameters.', + parameters: { + type: 'object', + properties: { + collection: { + type: 'string', + description: 'The name of the MongoDB collection to operate on.', + enum: ['Order', 'Product', 'User'], }, - "operation": { - "type": "string", - "description": "The type of operation to perform: 'find', 'findOne', 'create', 'update', or 'delete'.", - "enum": ["find", "findOne", "create", "update", "delete"] + operation: { + type: 'string', + description: + "The type of operation to perform: 'find', 'findOne', 'create', 'update', or 'delete'.", + enum: ['find', 'findOne', 'create', 'update', 'delete'], + }, + query: { + type: 'object', + description: 'The query parameters used for the operation.', }, - "query": { - "type": "object", - "description": "The query parameters used for the operation." - } }, - "required": ["collection", "operation", "query"], - "additionalProperties": false - } - } -} + required: ['collection', 'operation', 'query'], + additionalProperties: false, + }, + }, +}; export default mongoRepositoryTool; diff --git a/src/helper/gpt/tool/index.js b/src/helper/gpt/tool/index.js index 18042be..17804c0 100644 --- a/src/helper/gpt/tool/index.js +++ b/src/helper/gpt/tool/index.js @@ -1,7 +1,4 @@ -import aiRepositoryTool from './database/aiRepository.tool.js' -import getSchemasTool from './database/getSchemas.tool.js' +import aiRepositoryTool from './database/aiRepository.tool.js'; +import getSchemasTool from './database/getSchemas.tool.js'; -export default [ - aiRepositoryTool, - getSchemasTool, -] \ No newline at end of file +export default [aiRepositoryTool, getSchemasTool]; diff --git a/src/helper/gpt/tool/user/createUser.tool.js b/src/helper/gpt/tool/user/createUser.tool.js index 1881a66..9d02267 100644 --- a/src/helper/gpt/tool/user/createUser.tool.js +++ b/src/helper/gpt/tool/user/createUser.tool.js @@ -1,33 +1,36 @@ const createUserTool = { - "type": "function", - "function": { - "name": "create_user", - "description": "Create a new user with validated properties. (all fields must be in english)", - "parameters": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "User's name (must be a valid name)." + type: 'function', + function: { + name: 'create_user', + description: + 'Create a new user with validated properties. (all fields must be in english)', + parameters: { + type: 'object', + properties: { + name: { + type: 'string', + description: "User's name (must be a valid name).", }, - "birthDate": { - "type": "string", - "format": "date", - "description": "User's birth date (must be a valid date and the user must be over 18 years old)." + birthDate: { + type: 'string', + format: 'date', + description: + "User's birth date (must be a valid date and the user must be over 18 years old).", }, - "mobile": { - "type": "string", - "description": "User's mobile number (must be a valid number)." + mobile: { + type: 'string', + description: "User's mobile number (must be a valid number).", + }, + password: { + type: 'string', + description: + "User's password is required and must contain at least 6 digits.", }, - "password": { - "type": "string", - "description": "User's password is required and must contain at least 6 digits." - } }, - "required": ["name", "birthDate", "mobile", "password"], - "additionalProperties": false - } - } -} + required: ['name', 'birthDate', 'mobile', 'password'], + additionalProperties: false, + }, + }, +}; -export default createUserTool +export default createUserTool; diff --git a/src/helper/swagger/path/ai/ai.swagger.js b/src/helper/swagger/path/ai/ai.swagger.js index 2e83545..0e9e538 100644 --- a/src/helper/swagger/path/ai/ai.swagger.js +++ b/src/helper/swagger/path/ai/ai.swagger.js @@ -28,4 +28,4 @@ export default { }, }, }, -}; \ No newline at end of file +}; diff --git a/src/helper/swagger/swagger.config.js b/src/helper/swagger/swagger.config.js index 5f1aeb9..225b27d 100644 --- a/src/helper/swagger/swagger.config.js +++ b/src/helper/swagger/swagger.config.js @@ -16,17 +16,17 @@ const swaggerOptions = { }, ], }, - apis: ['./route/**/*.js'] + apis: ['./route/**/*.js'], }; const swaggerDocs = swaggerJsDoc(swaggerOptions); swaggerDocs.paths = { ...swaggerDocs.paths, - ...aiPath -} + ...aiPath, +}; -const setupSwagger = (app) => { +const setupSwagger = app => { app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocs)); }; diff --git a/src/main.js b/src/main.js index fa7e96c..cb9c890 100644 --- a/src/main.js +++ b/src/main.js @@ -10,19 +10,19 @@ import validateRequest from './middleware/ai/validateRequest.middleware.js'; const start = async () => { await startMongoose(); - + const app = express(); - + app.use(express.json()); - app.use(asyncHandler(validateRequest)) - app.use(asyncHandler(gptMiddleware)) - app.use(errorHandler) + app.use(asyncHandler(validateRequest)); + app.use(asyncHandler(gptMiddleware)); + app.use(errorHandler); - setupSwagger(app) + setupSwagger(app); app.listen(process.env.EXPRESS_PORT || 3000, () => { console.log(`Server running on port ${process.env.EXPRESS_PORT || 3000}`); }); -} +}; start().catch(console.error); diff --git a/src/middleware/ai/gpt.middleware.js b/src/middleware/ai/gpt.middleware.js index 8b285f4..de097c8 100644 --- a/src/middleware/ai/gpt.middleware.js +++ b/src/middleware/ai/gpt.middleware.js @@ -1,6 +1,7 @@ import aiService from '../../service/ai/ai.service.js'; -const context = 'You are an AI that manage api responses, you will receive body and header and analyse with your tools which function to call and send responses. Your response must always be in JSON in this format: { message, statusCode, status }. Important, don’t send JSON in the response'; +const context = + 'You are an AI that manage api responses, you will receive body and header and analyse with your tools which function to call and send responses. Your response must always be in JSON in this format: { message, statusCode, status }. Important, don’t send JSON in the response'; const gptMiddleware = async (req, res, _next) => { console.log(`Request Method: ${req.method}, Request URL: ${req.url}`); @@ -8,10 +9,17 @@ const gptMiddleware = async (req, res, _next) => { console.log(`Request Query: ${JSON.stringify(req.query)}`); console.log(`Request Headers: ${JSON.stringify(req.headers)}`); - const response = await aiService({ body: req.body, header: req.headers, query: req.query, method: req.method, url: req.url, firstContext: context }); + const response = await aiService({ + body: req.body, + header: req.headers, + query: req.query, + method: req.method, + url: req.url, + firstContext: context, + }); const { message, statusCode, status } = JSON.parse(response.content); res.status(statusCode).json({ status, message }); -} +}; -export default gptMiddleware; \ No newline at end of file +export default gptMiddleware; diff --git a/src/middleware/ai/gpt.middleware.test.js b/src/middleware/ai/gpt.middleware.test.js index d2166af..df86a93 100644 --- a/src/middleware/ai/gpt.middleware.test.js +++ b/src/middleware/ai/gpt.middleware.test.js @@ -39,16 +39,18 @@ describe('gptMiddleware', () => { status: 'success', message: 'Success', }); - - expect(aiService.default).toHaveBeenCalledWith(expect.objectContaining({ - body: { input: 'Hello, GPT!' }, - header: expect.objectContaining({ - authorization: 'Bearer token', - }), - query: {}, - method: 'POST', - url: '/gpt', - })); + + expect(aiService.default).toHaveBeenCalledWith( + expect.objectContaining({ + body: { input: 'Hello, GPT!' }, + header: expect.objectContaining({ + authorization: 'Bearer token', + }), + query: {}, + method: 'POST', + url: '/gpt', + }) + ); }); it('should handle errors from aiService', async () => { diff --git a/src/middleware/ai/validateRequest.middleware.js b/src/middleware/ai/validateRequest.middleware.js index 53bf38f..6f0bad7 100644 --- a/src/middleware/ai/validateRequest.middleware.js +++ b/src/middleware/ai/validateRequest.middleware.js @@ -1,21 +1,29 @@ import aiService from '../../service/ai/ai.service.js'; -const context = 'You are an AI that acts as a middleware for API request validation. You will receive the body, header, query, method, and URL of a request. Your task is to analyze whether all fields in the request adhere to established patterns and are present. If any field is invalid or missing, return an error message in JSON format: { message, statusCode, status }. If it is a creation (POST) or update (PUT) request, you must also check if the schema for that operation exists and is valid using the get_schemas function. If the request is valid and all fields are correct, return a success message in JSON format: { message: "OK", statusCode: "200", status: "OK" }. Important, don’t send ```JSON in the response.'; +const context = + 'You are an AI that acts as a middleware for API request validation. You will receive the body, header, query, method, and URL of a request. Your task is to analyze whether all fields in the request adhere to established patterns and are present. If any field is invalid or missing, return an error message in JSON format: { message, statusCode, status }. If it is a creation (POST) or update (PUT) request, you must also check if the schema for that operation exists and is valid using the get_schemas function. If the request is valid and all fields are correct, return a success message in JSON format: { message: "OK", statusCode: "200", status: "OK" }. Important, don’t send ```JSON in the response.'; -const validateRequest = async (req, res, next) => { +const validateRequest = async (req, res, next) => { console.log(`Request Method: ${req.method}, Request URL: ${req.url}`); console.log(`Request Body: ${JSON.stringify(req.body)}`); console.log(`Request Query: ${JSON.stringify(req.query)}`); console.log(`Request Headers: ${JSON.stringify(req.headers)}`); - const response = await aiService({ body: req.body, header: req.headers, query: req.query, method: req.method, url: req.url, firstContext: context }); + const response = await aiService({ + body: req.body, + header: req.headers, + query: req.query, + method: req.method, + url: req.url, + firstContext: context, + }); const { message, statusCode, status } = JSON.parse(response.content); console.log({ message, statusCode, status }); if (statusCode != 200) { return res.status(statusCode).json({ status, message }); } - next() -} + next(); +}; -export default validateRequest \ No newline at end of file +export default validateRequest; diff --git a/src/middleware/ai/validateRequest.middleware.test.js b/src/middleware/ai/validateRequest.middleware.test.js index 2fd040a..e042df1 100644 --- a/src/middleware/ai/validateRequest.middleware.test.js +++ b/src/middleware/ai/validateRequest.middleware.test.js @@ -36,16 +36,18 @@ describe('validateRequest Middleware', () => { expect(response.status).toBe(200); expect(response.text).toBe('OK'); - - expect(aiService.default).toHaveBeenCalledWith(expect.objectContaining({ - body: { input: 'Valid input' }, - header: expect.objectContaining({ - authorization: 'Bearer token', - }), - query: {}, - method: 'POST', - url: '/test', - })); + + expect(aiService.default).toHaveBeenCalledWith( + expect.objectContaining({ + body: { input: 'Valid input' }, + header: expect.objectContaining({ + authorization: 'Bearer token', + }), + query: {}, + method: 'POST', + url: '/test', + }) + ); }); it('should respond with an error message for invalid requests', async () => { diff --git a/src/middleware/error/asyncHandler.middlware.js b/src/middleware/error/asyncHandler.middlware.js index 4901cb7..539c1ac 100644 --- a/src/middleware/error/asyncHandler.middlware.js +++ b/src/middleware/error/asyncHandler.middlware.js @@ -1,7 +1,7 @@ -const asyncHandler = (fn) => { +const asyncHandler = fn => { return (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; }; -export default asyncHandler; \ No newline at end of file +export default asyncHandler; diff --git a/src/middleware/error/errorHandler.middlware.js b/src/middleware/error/errorHandler.middlware.js index b58b47d..06168b2 100644 --- a/src/middleware/error/errorHandler.middlware.js +++ b/src/middleware/error/errorHandler.middlware.js @@ -1,6 +1,6 @@ -const errorHandler = (err, _req, res, next) => { +const errorHandler = (err, _req, res, _next) => { console.error(err.stack); res.status(500).json({ status: 'error', message: 'Something went wrong!' }); }; -export default errorHandler; \ No newline at end of file +export default errorHandler; diff --git a/src/middleware/error/errorHandler.middlware.test.js b/src/middleware/error/errorHandler.middlware.test.js index f16785f..d057bc8 100644 --- a/src/middleware/error/errorHandler.middlware.test.js +++ b/src/middleware/error/errorHandler.middlware.test.js @@ -1,21 +1,20 @@ import express from 'express'; import request from 'supertest'; -import errorHandler from './errorHandler.middlware.js'; // Ajuste o caminho conforme necessário +import errorHandler from './errorHandler.middlware.js'; describe('errorHandler', () => { let app; beforeAll(() => { app = express(); - app.use(express.json()); // Middleware para parsear JSON + app.use(express.json()); - // Rota para simular erro app.get('/error', (req, res, next) => { const error = new Error('Test error'); - next(error); // Passa o erro para o middleware de erro + next(error); }); - app.use(errorHandler); // Usa o middleware de erro + app.use(errorHandler); }); it('should respond with status 500 and error message', async () => { @@ -29,11 +28,13 @@ describe('errorHandler', () => { }); it('should log the error stack', async () => { - const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(); // Mocka console.error + const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(); await request(app).get('/error'); - expect(consoleErrorSpy).toHaveBeenCalledWith(expect.stringContaining('Test error')); // Verifica se o erro foi logado - consoleErrorSpy.mockRestore(); // Restaura o método original + expect(consoleErrorSpy).toHaveBeenCalledWith( + expect.stringContaining('Test error') + ); + consoleErrorSpy.mockRestore(); }); }); diff --git a/src/route/index.js b/src/route/index.js index 4c7d830..2cb634f 100644 --- a/src/route/index.js +++ b/src/route/index.js @@ -5,4 +5,4 @@ const router = express.Router(); router.use(aiRouter); -export default router; \ No newline at end of file +export default router; diff --git a/src/service/ai/ai.service.js b/src/service/ai/ai.service.js index 79edbda..b986786 100644 --- a/src/service/ai/ai.service.js +++ b/src/service/ai/ai.service.js @@ -1,16 +1,26 @@ import gpt from '../../helper/gpt/config/gpt.config.js'; import functions from '../../service/index.js'; -const aiService = async ({ body, header, query, method, url, firstContext }) => { +const aiService = async ({ + body, + header, + query, + method, + url, + firstContext, +}) => { try { const context = [ - { - role: 'system', - content: firstContext - } + { + role: 'system', + content: firstContext, + }, ]; - context.push({ role: 'user', content: JSON.stringify({ body, header, query, method, url }) }); + context.push({ + role: 'user', + content: JSON.stringify({ body, header, query, method, url }), + }); let response = await gpt({ completion: context }); @@ -19,16 +29,20 @@ const aiService = async ({ body, header, query, method, url, firstContext }) => let functionResponse; if (!functions[name]) { - return { message: 'Function not found', statusCode: 404, status: 'error' }; // Retorna imediatamente + return { + message: 'Function not found', + statusCode: 404, + status: 'error', + }; // Retorna imediatamente } else { functionResponse = await functions[name](JSON.parse(args)); console.log(functionResponse); } - context.push({ - role: 'tool', - content: JSON.stringify(functionResponse), - tool_call_id: gpt.tool_calls[0].id + context.push({ + role: 'tool', + content: JSON.stringify(functionResponse), + tool_call_id: gpt.tool_calls[0].id, }); return null; // Retorna null se não houver erro } @@ -51,6 +65,6 @@ const aiService = async ({ body, header, query, method, url, firstContext }) => } catch (err) { return err.message; } -} +}; export default aiService; diff --git a/src/service/ai/ai.service.test.js b/src/service/ai/ai.service.test.js index 5545de5..a6ff9c2 100644 --- a/src/service/ai/ai.service.test.js +++ b/src/service/ai/ai.service.test.js @@ -38,12 +38,24 @@ describe('aiService', () => { gpt.mockResolvedValueOnce(mockGptResponse1); // Primeira chamada ao gpt gpt.mockResolvedValueOnce(mockGptResponse2); // Segunda chamada ao gpt - functions['mockFunction'] = vi.fn().mockResolvedValue({ message: 'Success', statusCode: 200, status: 'success' }); + functions['mockFunction'] = vi.fn().mockResolvedValue({ + message: 'Success', + statusCode: 200, + status: 'success', + }); - const response = await aiService({ body: mockBody, header: mockHeader, query: mockQuery, method: mockMethod, url: mockUrl }); + const response = await aiService({ + body: mockBody, + header: mockHeader, + query: mockQuery, + method: mockMethod, + url: mockUrl, + }); expect(gpt).toHaveBeenCalledTimes(2); // Verifica que gpt foi chamado duas vezes - expect(functions['mockFunction']).toHaveBeenCalledWith({ param1: 'value1' }); // Verifica se a função correta foi chamada + expect(functions['mockFunction']).toHaveBeenCalledWith({ + param1: 'value1', + }); // Verifica se a função correta foi chamada expect(response).toEqual(mockGptResponse2); // A última resposta deve ser a retornada }); @@ -62,16 +74,32 @@ describe('aiService', () => { }; gpt.mockResolvedValue(mockGptResponse); - const response = await aiService({ body: mockBody, header: mockHeader, query: mockQuery, method: mockMethod, url: mockUrl }); + const response = await aiService({ + body: mockBody, + header: mockHeader, + query: mockQuery, + method: mockMethod, + url: mockUrl, + }); - expect(response).toEqual({ message: 'Function not found', statusCode: 404, status: 'error' }); + expect(response).toEqual({ + message: 'Function not found', + statusCode: 404, + status: 'error', + }); }); it('should handle errors from gpt', async () => { const errorMessage = 'GPT error'; gpt.mockRejectedValue(new Error(errorMessage)); // Simula um erro na chamada do gpt - const response = await aiService({ body: mockBody, header: mockHeader, query: mockQuery, method: mockMethod, url: mockUrl }); + const response = await aiService({ + body: mockBody, + header: mockHeader, + query: mockQuery, + method: mockMethod, + url: mockUrl, + }); expect(response).toBe(errorMessage); // Verifica se a mensagem de erro foi retornada }); diff --git a/src/service/index.js b/src/service/index.js index e15d427..ead5728 100644 --- a/src/service/index.js +++ b/src/service/index.js @@ -4,4 +4,4 @@ import aiRepository from '../database/repository/ai.repository.js'; export default { get_schemas: getSchemas, ai_repository: aiRepository, -} \ No newline at end of file +}; diff --git a/src/service/schema/getSchemas.service.js b/src/service/schema/getSchemas.service.js index 40d9868..beac19f 100644 --- a/src/service/schema/getSchemas.service.js +++ b/src/service/schema/getSchemas.service.js @@ -1,12 +1,16 @@ -import { UserSchema, ProductSchema, OrderSchema } from '../../database/schema/index.js'; +import { + UserSchema, + ProductSchema, + OrderSchema, +} from '../../database/schema/index.js'; const schemas = { User: { - description: "Represents a user in the system.", + description: 'Represents a user in the system.', properties: UserSchema.obj, model: 'User', }, Product: { - description: "Represents a product in the inventory.", + description: 'Represents a product in the inventory.', properties: ProductSchema.obj, model: 'Product', }, @@ -22,7 +26,7 @@ const getSchemas = () => { name, description: schema.description, properties: schema.properties, - model: schema.model + model: schema.model, })); }; diff --git a/src/service/schema/getSchemas.service.test.js b/src/service/schema/getSchemas.service.test.js index 4866849..599589d 100644 --- a/src/service/schema/getSchemas.service.test.js +++ b/src/service/schema/getSchemas.service.test.js @@ -27,7 +27,7 @@ describe('getSchemas', () => { const expectedResult = [ { name: 'User', - description: "Represents a user in the system.", + description: 'Represents a user in the system.', properties: { username: { type: 'string' }, password: { type: 'string' }, @@ -36,7 +36,7 @@ describe('getSchemas', () => { }, { name: 'Product', - description: "Represents a product in the inventory.", + description: 'Represents a product in the inventory.', properties: { name: { type: 'string' }, price: { type: 'number' },