diff --git a/.gitignore b/.gitignore index 5c8cb8c..029d2fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Logs -page/ +page logs *.log npm-debug.log* @@ -91,6 +91,7 @@ out # Nuxt.js build / generate output .nuxt dist +dest # Gatsby files .cache/ diff --git a/api/exerciseRoutes.js b/api/exerciseRoutes.js index 5b7aa9a..988fd54 100644 --- a/api/exerciseRoutes.js +++ b/api/exerciseRoutes.js @@ -6,7 +6,7 @@ const Exercise = require("./Exercise"); // Configurações do Fuse.js const fuseOptions = { includeScore: true, - threshold: 0.4, + threshold: 0.5, keys: ["name.en", "name.pt"], }; @@ -52,6 +52,14 @@ const errorMessages = { en: "No exercises found.", pt: "Nenhum exercício encontrado.", }, + noTranslation: { + en: "Exercise not available in the selected language. Try:", + pt: "Exercício não disponível no idioma selecionado. Tente:", + }, + noSugestions:{ + en: "No suggestions available.", + pt: "Nenhuma sugestão disponível.", + }, fetchError: { en: "Error fetching exercises.", pt: "Erro ao buscar os exercícios.", @@ -243,52 +251,56 @@ router.get("/", async (req, res) => { } }); -// Rota para buscar exercícios +// Endpoint para buscar exercícios com sugestões de correção e tradução router.get("/search", async (req, res) => { const { query, lang = "en", fields } = req.query; - // Validar idioma + // Pegar o idioma do sistema (Accept-Language) caso lang seja inválido + let userLang = req.headers["accept-language"]?.split(",")[0].split("-")[0]; // Exemplo: "es-ES" -> "es" + + // Se lang for inválido, tenta usar o idioma do sistema, senão, define "en" como padrão + const selectedLang = ["en", "pt"].includes(lang) ? lang : (["en", "pt"].includes(userLang) ? userLang : "en"); + + // Se o idioma for inválido, retorna o erro na linguagem correta if (!["en", "pt"].includes(lang)) { - return res - .status(400) - .json({ message: errorMessages.invalidLang[lang] }); + return res.status(400).json({ message: errorMessages.invalidLang[selectedLang] }); } // Validar termo de busca if (!query) { - return res - .status(400) - .json({ message: errorMessages.missingQuery[lang] }); + return res.status(400).json({ message: errorMessages.missingQuery[lang] }); } try { // Recuperar todos os exercícios const exercises = await Exercise.find().lean(); - // Configurar Fuse.js com base no idioma - const fuse = new Fuse(exercises, { - ...fuseOptions, - keys: [`name.${lang}`], - }); - - // Realizar busca + // Configurar Fuse.js para busca + const fuse = new Fuse(exercises, fuseOptions); const results = fuse.search(query); - // Verificar se há resultados + // Se não houver correspondência, sugerir um nome parecido if (results.length === 0) { - return res.json({ message: errorMessages.noResults[lang] }); + const closestMatch = fuse.search(query, { limit: 1 })[0]; + return res.json({ + message: `${errorMessages.noResults[lang]} ${closestMatch ? closestMatch.item.name[lang] : `${errorMessages.noSugestions[lang]}`}`, + }); + } + + // Verificar se o exercício está disponível no idioma solicitado + const filteredResults = results.filter(result => result.item.name[lang]); + + if (filteredResults.length === 0) { + return res.json({ + message: `${errorMessages.noTranslation[lang]} ${results[0].item.name.en}`, + }); } - // Converter resultados e aplicar `fields`, se fornecido + // Processar os resultados com os campos solicitados const fieldList = fields ? fields.split(",") : null; - const matchedExercises = results.map((result) => { + const matchedExercises = filteredResults.map(result => { const exercise = result.item; - - // Montar resposta com todos os campos ou apenas os selecionados - const response = { - _id: exercise._id, - name: exercise.name[lang], - }; + const response = { _id: exercise._id, name: exercise.name[lang] }; if (!fieldList || fieldList.includes("force")) { response.force = exercise.force[lang]; @@ -324,13 +336,9 @@ router.get("/search", async (req, res) => { return response; }); - res.json({ message: errorMessages.resultesFound[lang], exercises: matchedExercises }); + res.json({ exercises: matchedExercises }); } catch (err) { - console.error( - lang === "pt" - ? `${errorMessages.searchError.pt}: ${err.message}` - : `${errorMessages.searchError.en}: ${err.message}` - ); + console.error(`${errorMessages.searchError[lang]}: ${err.message}`); res.status(500).json({ message: errorMessages.searchError[lang], error: err.message, diff --git a/package-lock.json b/package-lock.json index 65d5784..921474b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "node": "^23.7.0", "node-fetch": "^3.3.2", "npm": "^11.1.0", + "path": "^0.12.7", "swagger-ui-express": "^5.0.1" }, "devDependencies": { @@ -369,6 +370,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -535,6 +537,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -589,6 +592,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -601,6 +605,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -1336,28 +1341,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -1394,17 +1377,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1559,6 +1536,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, "node_modules/js-yaml": { @@ -1839,11 +1817,6 @@ "version": "2.0.0", "license": "MIT" }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -4551,6 +4524,16 @@ "node": ">= 0.8" } }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "license": "MIT", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -4627,6 +4610,15 @@ "node": ">=6.0.0" } }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "license": "MIT", @@ -4669,27 +4661,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "license": "MIT", @@ -4759,6 +4730,7 @@ "version": "7.7.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.0.tgz", "integrity": "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4952,6 +4924,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4969,38 +4942,6 @@ "@scarf/scarf": "=1.4.0" } }, - "node_modules/swagger-ui-express": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", - "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", - "license": "MIT", - "dependencies": { - "swagger-ui-dist": ">=5.0.0" - }, - "engines": { - "node": ">= v0.10.32" - }, - "peerDependencies": { - "express": ">=4.0.0 || >=5.0.0-beta" - } - }, - "node_modules/synckit": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", - "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", - "dev": true, - "license": "MIT", - "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/swagger-ui-express": { "version": "5.0.1", "license": "MIT", @@ -5029,13 +4970,6 @@ "url": "https://opencollective.com/unts" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -5131,6 +5065,21 @@ "punycode": "^2.1.0" } }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "license": "MIT", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC" + }, "node_modules/utils-merge": { "version": "1.0.1", "license": "MIT", @@ -5174,6 +5123,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" diff --git a/package.json b/package.json index f02c822..b788182 100644 --- a/package.json +++ b/package.json @@ -2,57 +2,58 @@ "name": "musclelib-api", "version": "1.1.2", "description": "MuscleLib API is a RESTful API for searching exercises, with support for multiple languages and pagination. It was built using Node.js and Express.js, and it uses a MongoDB database to store the exercises. The API is documented using Swagger, and it has a simple and intuitive structure. The API is free to use and it does not require any authentication to access the data. The API is also very fast and it can handle a high volume of requests. The API is also very scalable and it can be easily integrated with other applications.", - "main": "server.js", + "main": "server/start.js", "scripts": { - "start": "node server.js", - "dev": "nodemon server.js", - "test": "echo \"Error: no test specified\" && exit 1", - "lint": "eslint .", - "format": "prettier --write ." + "start": "node server/start.js", + "dev": "nodemon server/start.js", + "test": "echo \"Error: no test specified\" && exit 1", + "lint": "eslint .", + "format": "prettier --write ." }, "author": "Daniel", "license": "MIT", "repository": { - "type": "git", - "url": "https://github.com/programador-jr/MuscleLibAPI.git" + "type": "git", + "url": "https://github.com/programador-jr/MuscleLibAPI.git" }, "keywords": [ - "api", - "exercises", - "fitness", - "health", - "musclelib", - "nodejs", - "expressjs", - "mongodb", - "restful", - "swagger", - "json", - "pagination", - "multilanguage" + "api", + "exercises", + "fitness", + "health", + "musclelib", + "nodejs", + "expressjs", + "mongodb", + "restful", + "swagger", + "json", + "pagination", + "multilanguage" ], "dependencies": { - "axios": "^1.7.9", - "body-parser": "^1.20.3", - "cors": "^2.8.5", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "fuse.js": "^7.0.0", - "mongoose": "^8.9.6", - "swagger-ui-express": "^5.0.1", - "node-fetch": "^3.3.2", - "node": "^23.7.0", - "npm": "^11.1.0" + "axios": "^1.7.9", + "body-parser": "^1.20.3", + "cors": "^2.8.5", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "fuse.js": "^7.0.0", + "mongoose": "^8.9.6", + "node": "^23.7.0", + "node-fetch": "^3.3.2", + "npm": "^11.1.0", + "path": "^0.12.7", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { - "nodemon": "^3.1.9", - "eslint": "^9.19.0", - "eslint-config-prettier": "^10.0.1", - "eslint-plugin-prettier": "^5.2.3", - "prettier": "^3.4.2" + "eslint": "^9.19.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.3", + "nodemon": "^3.1.9", + "prettier": "^3.4.2" }, "bugs": { - "url": "https://github.com/programador-jr/MuscleLibAPI/issues" + "url": "https://github.com/programador-jr/MuscleLibAPI/issues" }, "homepage": "https://github.com/programador-jr/MuscleLibAPI#readme" - } \ No newline at end of file +} diff --git a/server.js b/server.js deleted file mode 100644 index 60aba82..0000000 --- a/server.js +++ /dev/null @@ -1,41 +0,0 @@ -const express = require("express"); -const mongoose = require("mongoose"); -const cors = require("cors"); -const exerciseRoutes = require("./api/exerciseRoutes"); -require('dotenv').config() - -const dbURI = process.env.MONGODB_URI; -const PORT = process.env.PORT || 8080; - -const app = express(); - -mongoose.set("strictQuery", false); - -// Conexão com MongoDB -mongoose.connect(dbURI, { - socketTimeoutMS: 45000, // Tempo limite do socket (opcional) - serverSelectionTimeoutMS: 45000 // Tempo para seleção do servidor -}) -.then(() => console.log('MongoDB conectado com sucesso!')) -.catch(err => console.log('Erro ao conectar ao MongoDB:', err)); - - -// Middleware -app.use(cors()); -app.use(express.json()); -app.use("/exercises", express.static(__dirname + "/exercises")); -app.use("/api/exercises", exerciseRoutes); // Usar rotas atualizadas -//app.use("/page", express.static(__dirname + "/page")); - -app.get("/", (req, res) => { - res.send("API em execução..."); - // res.sendFile(__dirname + '/page/index.html'); -}); - -/*app.use((req, res, next) => { - res.status(404).sendFile(__dirname + '/page/404.html'); -});*/ - -app.listen(PORT, () => { - console.log(`Servidor rodando na porta ${PORT}`); -}); diff --git a/server/start.js b/server/start.js new file mode 100644 index 0000000..15d0691 --- /dev/null +++ b/server/start.js @@ -0,0 +1,5 @@ +const path = require('path'); + +const serverPath = path.join(__dirname, '../dest/server.js'); + +require(serverPath) \ No newline at end of file diff --git a/vercel.json b/vercel.json index be79a61..ffcfd57 100644 --- a/vercel.json +++ b/vercel.json @@ -1,14 +1,16 @@ { "version": 2, "builds": [ - { "src": "server.js", - "use": "@vercel/node" - } + { + "src": "server/start.js", + "use": "@vercel/node" + } ], "routes": [ - { - "src": "(.*)", - "dest": "server.js" - } + { + "src": "/(.*)", + "methods": ["GET"], + "dest": "/server/start.js" + } ] -} \ No newline at end of file + } \ No newline at end of file