UNPKG

@tiledesk/tiledesk-server

Version:
378 lines (303 loc) 13 kB
var express = require('express'); var router = express.Router(); var { KB, Namespace } = require('../models/kb_setting'); var winston = require('../config/winston'); const JobManager = require('../utils/jobs-worker-queue-manager/JobManagerV2'); const { AiReindexService } = require('../services/aiReindexService'); const { Webhook } = require('../models/webhook'); const webhookService = require('../services/webhookService'); const errorCodes = require('../errorCodes'); const aiManager = require('../services/aiManager'); var ObjectId = require('mongoose').Types.ObjectId; const default_embedding = require('../config/kb/embedding'); const port = process.env.PORT || '3000'; let TILEBOT_ENDPOINT = "http://localhost:" + port + "/modules/tilebot/"; if (process.env.TILEBOT_ENDPOINT) { TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + "/" } winston.debug("TILEBOT_ENDPOINT: " + TILEBOT_ENDPOINT); const KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken'; const AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL; const JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer'; let jobManager = new JobManager(AMQP_MANAGER_URL, { debug: false, topic: JOB_TOPIC_EXCHANGE, exchange: JOB_TOPIC_EXCHANGE }) jobManager.connectAndStartPublisher((status, error) => { if (error) { winston.error("connectAndStartPublisher error: ", error); } else { winston.info("KbRoute - ConnectPublisher done with status: ", status); } }) let default_engine = { name: "pinecone", type: process.env.PINECONE_TYPE || "pod", apikey: "", vector_size: 1536, index_name: process.env.PINECONE_INDEX } let default_engine_hybrid = { name: "pinecone", type: process.env.PINECONE_TYPE_HYBRID || "serverless", apikey: "", vector_size: 1536, index_name: process.env.PINECONE_INDEX_HYBRID } router.post('/kb/reindex', async (req, res) => { winston.verbose("/kb/reindex webhook called") winston.debug("(webhook) req.body: ", req.body); if (!req.headers['x-auth-token']) { winston.error("(webhook) Unauthorized: A x-auth-token must be provided") return res.status(401).send({ success: false, error: "Unauthorized", message: "A x-auth-token must be provided" }) } if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) { winston.error("(webhook) Unauthorized: You don't have the authorization to accomplish this operation") return res.status(401).send({ success: false, error: "Unauthorized", message: "You don't have the authorization to accomplish this operation" }); } let content_id = req.body.content_id; let kb; try { kb = await KB.findById(content_id); } catch (err) { winston.error("(webhook) Error getting kb content: ", err); return res.status(500).send({ success: false, error: "Error getting content with id " + content_id }); } if (!kb) { winston.warn("(webhook) Kb content not found with id " + content_id + ". Deleting scheduler..."); // Assuming the content has been deleted. The scheduler should be stopped and deleted. res.status(200).send({ success: true, message: "Content no longer exists. Deleting scheduler..." }) setTimeout( async () => { let aiReindexService = new AiReindexService(); let deleteResponse = await aiReindexService.delete(content_id).catch((err) => { winston.error("(webhook) Error deleting scheduler ", err); winston.error("(webhook) Error deleting scheduler " + err); return; }); winston.verbose("(webhook) delete response: ", deleteResponse); return; }, 10000); return; } const namespace_id = kb.namespace; let namespace; try { namespace = await aiManager.checkNamespace(kb.id_project, namespace_id); } catch (err) { let errorCode = err?.errorCode ?? 500; return res.status(errorCode).send({ success: false, error: err.error }); } if (kb.type === 'sitemap') { const urls = await aiManager.fetchSitemap(kb.source).catch((err) => { winston.error("(webhook) Error fetching sitemap: ", err); return res.status(500).send({ success: false, error: err }); }) if (urls.length === 0) { return res.status(400).send({ success: false, error: "No url found on sitemap" }); } let existingKbs; try { existingKbs = await KB.find({ id_project: kb.id_project, namespace: namespace_id, sitemap_origin_id: content_id}).lean().exec(); } catch(err) { winston.error("(webhook) Error finding existing contents: ", err); return res.status(500).send({ success: false, error: "Error finding existing sitemap contents" }); } const result = await aiManager.foundSitemapChanges(existingKbs, urls).catch((err) => { winston.error("(webhook) error finding sitemap differecens ", err); return res.status(400).send({ success: false, error: "Error finding sitemap differecens" }); }) if (!result) return; // esco qui const { addedUrls, removedIds } = result; if (removedIds.length > 0) { const idsSet = new Set(removedIds); const kbsToDelete = existingKbs.filter(obj => idsSet.has(obj._id)); aiManager.removeMultipleContents(namespace, kbsToDelete).catch((err) => { winston.error("(webhook) error deleting multiple contents: ", err); }) } if (addedUrls.length > 0) { const options = { sitemap_origin_id: kb.id, sitemap_origin: kb.source, scrape_type: kb.scrape_type, scrape_options: kb.scrape_options, refresh_rate: kb.refresh_rate, ...(kb.tags ? { tags: kb.tags } : {}), ...(kb.situated_context === true && (kb.scrape_type === 0 || kb.scrape_type === '0') ? { situated_context: true } : {}), } aiManager.addMultipleUrls(namespace, addedUrls, options).catch((err) => { winston.error("(webhook) error adding multiple urls contents: ", err); }) } res.status(200).send({ success: true, message: "Content queued for reindexing" }); } else { let json = { id: kb._id, type: kb.type, source: kb.source, content: "", namespace: kb.namespace, refresh_rate: kb.refresh_rate, last_refresh: kb.last_refresh } if (kb.scrape_type) { json.scrape_type = kb.scrape_type } if (kb.scrape_options) { json.parameters_scrape_type_4 = { tags_to_extract: kb.scrape_options.tags_to_extract, unwanted_tags: kb.scrape_options.unwanted_tags, unwanted_classnames: kb.scrape_options.unwanted_classnames } } let namespace = await Namespace.findOne({ id: kb.namespace }).catch((err) => { winston.error("(webhook) Error getting namespace ", err) return res.status(500).send({ success: false, error: err }) }) if (!namespace) { winston.warn("(webhook) Namespace not found with id " + kb.namespace); return res.status(500).send({ success: false, error: err }) } json.engine = namespace.engine || (namespace.hybrid ? default_engine_hybrid : default_engine); json.hybrid = namespace.hybrid; let embedding = namespace.embedding || default_embedding; embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY; json.embedding = embedding; const situated_context_obj = aiManager.normalizeSituatedContext(kb.situated_context); if (situated_context_obj) { json.situated_context = situated_context_obj; } let resources = []; resources.push(json); if (process.env.NODE_ENV !== 'test') { aiManager.scheduleScrape(resources, namespace.hybrid); } res.status(200).send({ success: true, message: "Content queued for reindexing" }); } }) router.post('/kb/status', async (req, res) => { winston.info("(webhook) kb status called"); winston.info("(webhook) req.body: ", req.body); winston.info("(webhook) x-auth-token: " + req.headers['x-auth-token']); winston.info("(webhook) KB_WEBHOOK_TOKEN: " + KB_WEBHOOK_TOKEN); if (!req.headers['x-auth-token']) { winston.error("Unauthorized: A x-auth-token must be provided") return res.status(401).send({ success: false, error: "Unauthorized", message: "A x-auth-token must be provided" }) } if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) { winston.error("Unauthorized: You don't have the authorization to accomplish this operation") return res.status(401).send({ success: false, error: "Unauthorized", message: "You don't have the authorization to accomplish this operation" }); } let body = req.body; winston.verbose('/webhook kb status body: ', body); let kb_id = body.id; winston.verbose('/webhook kb status body: ' + kb_id); let update = {}; if (body.status) { update.status = body.status; } KB.findByIdAndUpdate(kb_id, update, { new: true }, (err, kb) => { if (err) { winston.error(err); return res.status(500).send({ success: false, error: err }); } if (!kb) { winston.info("Knwoledge Base content to be updated not found") return res.status(404).send({ success: false, messages: "Knwoledge Base content not found with id " + kb_id }); } winston.info("kb updated succesfully: ", kb); res.status(200).send(kb); }) }) router.all('/:webhook_id', async (req, res) => { let webhook_id = req.params.webhook_id; let payload = req.body; payload.webhook_http_method = req.method; let params = req.query; let dev = params.dev; delete params.dev; if (params) { payload.webhook_query_params = params; } let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => { winston.error("Error finding webhook: ", err); return res.status(500).send({ success: false, error: err }); }) if (!webhook) { winston.warn("Webhook not found with id " + webhook_id); return res.status(404).send({ success: false, error: "Webhook not found with id " + webhook_id }); } if (!webhook.enabled) { winston.verbose("Webhook " + webhook_id + " is currently turned off") return res.status(422).send({ success: false, error: "Webhook " + webhook_id + " is currently turned off"}) } const rate_manager = req.app.get("rate_manager"); const allowed = await rate_manager.canExecute(webhook.id_project, null, 'webhook'); if (!allowed) { winston.warn("Webhook rate limit exceeded for project " + webhook.id_project) return res.status(429).send({ message: "Rate limit exceeded"}); } payload.request_id = "automation-request-" + webhook.id_project + "-" + new ObjectId() + "-" + webhook_id; // To delete - Start // This endpoint will be used only for production webhooks, so is no longer // necessary to pass dev and redis_client to webhookService.run(). let redis_client = req.app.get('redis_client'); // and substitute currect run with the following one //webhookService.run(webhook, payload) // To delete - End webhookService.run(webhook, payload, dev, redis_client).then((response) => { return res.status(200).send(response); }).catch((err) => { if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) { return res.status(422).send({ success: false, message: "Development webhook is currently turned off", code: err.code }) } else { let status = err.status || 500; return res.status(status).send(err.data); } }) }) router.all('/:webhook_id/dev', async (req, res) => { let webhook_id = req.params.webhook_id; let payload = req.body; payload.webhook_http_method = req.method; let params = req.query; delete params.dev; if (params) { payload.webhook_query_params = params; } let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => { winston.error("Error finding webhook: ", err); return res.status(500).send({ success: false, error: err }); }) if (!webhook) { winston.warn("Webhook not found with id " + webhook_id); return res.status(404).send({ success: false, error: "Webhook not found with id " + webhook_id }); } let redis_client = req.app.get('redis_client'); webhookService.run(webhook, payload, true, redis_client).then((response) => { return res.status(200).send(response); }).catch((err) => { if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) { return res.status(422).send({ success: false, message: "Development webhook is currently turned off", code: err.code }) } else { let status = err.status || 500; return res.status(status).send(err.data); } }) }) // async function generateChatbotToken(chatbot) { // let signOptions = { // issuer: 'https://tiledesk.com', // subject: 'bot', // audience: 'https://tiledesk.com/bots/' + chatbot._id, // jwtid: uuidv4() // }; // let botPayload = chatbot.toObject(); // let botSecret = botPayload.secret; // var bot_token = jwt.sign(botPayload, botSecret, signOptions); // return bot_token; // } module.exports = router;