[
  {
    "path": ".circleci/config.yml",
    "content": "# Use the latest 2.1 version of CircleCI pipeline process engine. \n# See: https://circleci.com/docs/2.0/configuration-reference\nversion: 2.1\n\norbs:\n  # The Node.js orb contains a set of prepackaged CircleCI configuration you can utilize\n  # Orbs reduce the amount of configuration required for common tasks. \n  # See the orb documentation here: https://circleci.com/developer/orbs/orb/circleci/node\n  node: circleci/node@4.1\n\n\njobs:\n  # Below is the definition of your job to build and test your app, you can rename and customize it as you want.\n  build-and-test:  \n    # These next lines define a Docker executor: https://circleci.com/docs/2.0/executor-types/\n    # You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub.\n    # A list of available CircleCI Docker Convenience Images are available here: https://circleci.com/developer/images/image/cimg/node\n    docker:\n      - image: cimg/node:16.17.1\n    # Then run your tests!\n    # CircleCI will report the results back to your VCS provider.\n    steps:\n      # Checkout the code as the first step.\n      - checkout\n      # Next, the node orb's install-packages step will install the dependencies from a package.json.\n      # The orb install-packages step will also automatically cache them for faster future runs.\n      - node/install-packages\n      # If you are using yarn instead npm, remove the line above and uncomment the two lines below.\n      # - node/install-packages:\n      #     pkg-manager: yarn \n      - restore_cache:\n          keys:\n            # when lock file changes, use increasingly general patterns to restore cache\n            - node-v1-{{ .Branch }}-{{ checksum \"package-lock.json\" }}\n            - node-v1-{{ .Branch }}-\n            - node-v1-\n      - save_cache:\n          paths:\n            - ~/usr/local/lib/node_modules  # location depends on npm version\n          key: node-v1-{{ .Branch }}-{{ checksum \"package-lock.json\" }}\n      - run:\n          name: Run tests\n          command: npm test\n\nworkflows:\n  # Below is the definition of your workflow.\n  # Inside the workflow, you provide the jobs you want to run, e.g this workflow runs the build-and-test job above.\n  # CircleCI will run this workflow on every commit.\n  # For more details on extending your workflow, see the configuration docs: https://circleci.com/docs/2.0/configuration-reference/#workflows\n  sample: \n    jobs:\n      - build-and-test\n      # For running simple node tests, you could optionally use the node/test job from the orb to replicate and replace the job above in fewer lines.\n      # - node/test\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\nnpm-debug.log\n.firebasekey.json\nconfig/.firebase-key"
  },
  {
    "path": ".github/workflows/docker-community-profiler-latest.yml",
    "content": "name: Docker Image Community Profiler latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master ]\t\n\njobs:\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Check out the repo\n      uses: actions/checkout@v4\n    \n    - name: Login to Docker Hub\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n    \n    - name: Build and push\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        file: ./Dockerfile-profiler\n        push: true\n        tags: tiledesk/tiledesk-server:latest-profiler\n\n"
  },
  {
    "path": ".github/workflows/docker-community-push-latest.yml",
    "content": "name: Docker Image Community latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master ]\t\n\njobs:\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Check out the repo\n      uses: actions/checkout@v4\n    \n    - name: Login to Docker Hub\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n    - name: Build and push\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        file: ./Dockerfile\n        push: true\n        tags: tiledesk/tiledesk-server:latest\n"
  },
  {
    "path": ".github/workflows/docker-community-worker-push-latest.yml",
    "content": "name: Docker Image Community Worker latest CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master ]\t\n\njobs:\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Check out the repo\n      uses: actions/checkout@v4\n    \n    - name: Login to Docker Hub\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n    \n    - name: Build and push\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        file: ./Dockerfile-jobs\n        push: true\n        tags: tiledesk/tiledesk-server-worker:latest\n"
  },
  {
    "path": ".github/workflows/docker-image-en-tag-push.yml",
    "content": "name: Publish Docker Enterprise Tag image\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every tag including hierarchical tags like\njobs:\n\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n    steps:\n     - name: Check out the repo\n       uses: actions/checkout@v4\n      \n     - name: Login to Docker Hub\n       uses: docker/login-action@v3\n       with:\t    \n        username: ${{ secrets.DOCKER_USERNAME }}\n        password: ${{ secrets.DOCKER_PASSWORD }}\n    \n     - name: Log Voice Token\n       run: |\n          echo \"Voice token log: ${{ secrets.VOICE_TOKEN }}\"\n    \n     - name: Log Voice Twilio Token\n       run: |\n          echo \"Voice Twilio token log: ${{ secrets.VOICE_TWILIO_TOKEN }}\"\n\n     - name: Log Voice Enghouse Token\n       run: |\n          echo \"Voice Enghouse token log: ${{ secrets.VOICE_ENGHOUSE_TOKEN }}\"\n\n     - name: Generate Docker metadata\n       id: meta\n       uses: docker/metadata-action@v5\n       with:\n        images: tiledeskrepo/tiledesk-server-enterprise\n        tags: |\n          type=ref,event=branch\n          type=semver,pattern={{version}}\n\n     - name: Push to Docker Hub\n       uses: docker/build-push-action@v6\n       with:\n        context: .\n        file: ./Dockerfile-en\n        push: true\n        build-args: |\n          NPM_TOKEN=${{ secrets.NPM_TOKEN }}\n          VOICE_TOKEN=${{ secrets.VOICE_TOKEN }}\n          VOICE_TWILIO_TOKEN=${{ secrets.VOICE_TWILIO_TOKEN }}\n          VOICE_ENGHOUSE_TOKEN=${{ secrets.VOICE_ENGHOUSE_TOKEN }}\n        tags: ${{ steps.meta.outputs.tags }}"
  },
  {
    "path": ".github/workflows/docker-image-tag-community-tag-push.yml",
    "content": "name: Publish Docker Community image tags\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every tag including hierarchical tags like\njobs:\n\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n    steps:\n     - name: Check out the repo\n       uses: actions/checkout@v4\n\n     - name: Login to Docker Hub\n       uses: docker/login-action@v3\n       with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n    \n     - name: Generate Docker metadata\n       id: meta\n       uses: docker/metadata-action@v5\n       with:\n        images: tiledesk/tiledesk-server\n        tags: |\n          type=ref,event=branch\n          type=semver,pattern={{version}}\n\n     - name: Build and push\n       uses: docker/build-push-action@v6\n       with:\n        context: .\n        file: ./Dockerfile\n        push: true\n        tags: ${{ steps.meta.outputs.tags }}\n\n"
  },
  {
    "path": ".github/workflows/docker-image-tag-worker-community-tag-push.yml",
    "content": "name: Publish Docker Community Worker image tags\n\non:\n  push:\n    tags:\n      - '**'           # Push events to every tag including hierarchical tags like\njobs:\n\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n    steps:\n     - name: Check out the repo\n       uses: actions/checkout@v4\n\n     - name: Login to Docker Hub\n       uses: docker/login-action@v3\n       with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n  \n     - name: Generate Docker metadata\n       id: meta\n       uses: docker/metadata-action@v5\n       with:\n        images: tiledesk/tiledesk-server-worker\n        tags: |\n          type=ref,event=branch\n          type=semver,pattern={{version}}\n  \n     - name: Build and push\n       uses: docker/build-push-action@v6\n       with:\n        context: .\n        file: ./Dockerfile-jobs\n        push: true\n        tags: ${{ steps.meta.outputs.tags }}"
  },
  {
    "path": ".github/workflows/docker-push-en-push-latest.yml",
    "content": "name: Docker Enterprise Latest Image CI\n\non:\t\n  push:\t \n    branches: [ master ]\n  pull_request:\t\n    branches: [ master ]\t\n\njobs:\n  push_to_registry:\n    name: Push Docker image to Docker Hub\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Check out the repo\n      uses: actions/checkout@v4\n    \n    - name: Login to Docker Hub\n      uses: docker/login-action@v3\n      with:\n        username: ${{ secrets.DOCKERHUB_USERNAME }}\n        password: ${{ secrets.DOCKERHUB_TOKEN }}\n\n    - name: Build and push\n      uses: docker/build-push-action@v6\n      with:\n        context: .\n        file: ./Dockerfile-en\n        push: true\n        build-args: |\n          NPM_TOKEN=${{ secrets.NPM_TOKEN }}\n        tags: tiledeskrepo/tiledesk-server-enterprise\n\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nconfig/.firebase-key\n.env.list\ndata\n.firebasekey-pre.json\n.firebasekey.json\n.env\ncopyfiles\nlogs"
  },
  {
    "path": ".glitch-assets",
    "content": ""
  },
  {
    "path": ".npmrc_",
    "content": "//registry.npmjs.org/:_authToken=${NPM_TOKEN}\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\ndist: bionic\nnode_js:\n- '12.20.2'\nservices: mongodb\nsudo: required\ncache:\n  directories:\n  - node_modules\nenv:\n- MONGODB_VERSION=4.0.x MONGODB_TOPOLOGY=standalone\nbefore_install:\n- npm run enable-ent\n- openssl aes-256-cbc -K $encrypted_f39920166870_key -iv $encrypted_f39920166870_iv\n  -in .firebasekey.json.enc -out .firebasekey.json -d\n# deploy:\n#   provider: herokuDISABLED\n#   api_key:\n#     secure: Lii7UafzO+WY0b3lAHk+OOgwpPC7jbb+ZuXFibtCawM3RyPNZj91m0on+rOM/k8+s6p/moD7VYoaWtwxjnPhjJsXf8kfbZTOq8d3dhW8qP8o3VBmNbwJGFpHbYPg7UMRaTf2opyNdbreuCV5/19WKSeLjvhBM2TOyFxKJIGevTNjBRJoQI48E5+ciaCzK29jkMq7LgIPUgm+V+hxkYM3DK4BuK+NR6FzLtAkcAJJMthcl82L7NtykA0k+RbeeYYdiPV7dVMvv58v5ifPoL//8JtBNvGPeVJkwPmz2c9Nej9kAURLW5vITFKksHt95yb1pvWG3b9lvkMoLgEkYmdS/O6Jlvoke6FaNnaH7FE3IQEfJ141FO81zqgZ15+LPeAyrAesKnr3f5hR+7iL1f3sVR8cGpCP7fGy2HNXQZCcNC5y2djX8NQdyjWCSxqMOHen6Dww8IQsmTK6sVVpLBtDnaDTpLXTv994RjEk7RCCSp5hXCjE1sNPRPiv/eqPyFt22jxwLSuUnjAR3qE/IpbGlhs6UWInftoqwBOT0+8NA92dCWq8pRoVAZ4/aD1Sph/VjRAVlIwKrytYpV89MjYhLJv02Zt3iEvoy5RUYBL8OYJbDpcq4BvU73aZ1C5PTNPBy3/8ieHR2KKeabqC1YpFBtHJ131Zn5JLvdArakHmaio=\n#   app: tiledesk-server-pre\n#   on:\n#     repo: Tiledesk/tiledesk-server\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "\n\n💥 TILEDESK SERVER v2.3.77 💥\n🚀        TAGGED AND PUBLISHED ON NPM           🚀\n🚀        IN PRODUCTION                        🚀\n(https://www.npmjs.com/package/@tiledesk/tiledesk-server/v/2.3.77) \n\n# 2.18.3\n- Added permissions logic\n- Added custom roles support\n- Add answered questions functionality\n- Added AnsweredQuestion schema with TTL index for automatic deletion.\n- Updated UnansweredQuestion schema to include additional fields and improved query handling for searching and sorting.\n- Enhanced the deletion process for unanswered questions.\n\n# 2.17.4\n- Refactor error handling and code structure in webhook.js\n\n# 2.17.3\n- Added missing import path on kb route\n\n# 2.17.2\n- Added support for situated context in kb route\n- Added RAG context management to KB routes\n- Added support for scrape type 0 (alias: trafilatura)\n\n# 2.16.2\n- Improved multiplier retrieval for model types in quotes route\n\n# 2.16.1\n- Added stream option support to the KB /qa endpoint for real-time responses\n- Enhanced file upload route to correctly handle .webm files\n- Optimized token consumption and management in knowledge base operations\n\n# 2.16.0-hf\n- Fixed bug: issue on audio sent from widget\n\n# 2.16.0\n- Added possibility to update Knowledge Base content\n- Added rated only filter in Conversations History\n- Improved pending requests management\n\n# 2.15.8\n- Updated tybot-connector to 2.0.45\n- Added support for tags management in knowledge base routes\n\n# 2.15.7\n- Updated whatsapp-connector to 1.0.26\n\n# 2.15.6\n- Updated voice-twilio-connector to 0.3.2\n- Updated vxml-connector to 0.1.91\n\n# 2.15.5\n- Fixed email flooding when smart assignment is active and there are no operators available\n\n# 2.15.4\n- Updated endpoint to get project users adding the query parameter \"trashed\" in order to obtain also the trashed users.\n- Added endpoint /restore to restore a deleted project user\n\n# 2.15.3\n- Updated whatsapp-connector to 1.0.25\n- Updated sms-connector to 0.1.13\n- Bug fix: join a conversation with a note without text.\n- Added phone number filter when searching for conversations in history\n- Added migration script to add the contact field in request object improving the search by phone number\n\n# 2.15.2\n- Updated GitHub actions\n\n# 2.15.1\n- Updated whatsapp-connector to 1.0.24\n\n# 2.15.0\n- Updated whatsapp-connector to 1.0.23\n- Fix logout with Google Signin method\n\n# 2.14.28\n- Add audio MIME type equivalences for MPEG, MP3, and Opus formats\n\n# 2.14.26\n- Added endpoints to connect with a MCP Tools and get tools list.\n- Updated tybot-connectort to 2.0.44\n\n# 2.14.25\n- Deprecate user file upload routes in files.js and images.js\n\n# 2.14.24\n- Improved extension management on file uploading to support .wav and .svg\n\n# 2.14.23\n- Updated whatsapp-connector to 1.0.22\n- Added new endpoint for files uploading \n\n# 2.14.22 - aborted\n- Updated whatsapp-connector to 1.0.20\n- Added new endpoint for files uploading \n\n# 2.14.21 - aborted\n- Updated whatsapp-connector to 1.0.20\n- Added new endpoint for files uploading \n\n# 2.14.20 - aborted\n- Updated whatsapp-connector to 1.0.19\n- Added new endpoint for files uploading \n\n# 2.14.18\n- Bug fix already existing email when login from google \n\n# 2.14.17\n- Bug fix google strategy passport\n\n# 2.14.14\n- Updated tybot-connector to 2.0.43\n\n# 2.14.13\n- Added sitemap scheduling in aiManager\n- Updated import route to use sitemap scheduling functionality\n\n# 2.14.12\n- Updated handling of unresponsive requests to close based on the updatedAt field instead of createdAt\n\n# 2.14.10\n- Added support for a custom reranking_multiplier parameter in the /qa endpoint\n- Improved DB performance by optimizing the query used to find conversations to be closed automatically\n\n# 2.14.9\n- Improved the requests search function and added the ability to specify a timezone when searching\n\n# 2.14.8\n- Added extraction of namespace_id from request body in scrape status route\n\n# 2.14.7\n- Added embedding configuration to namespace import route\n\n# 2.14.6\n- Fix namespace checking in export route and improve error handling\n\n# 2.14.5\n- Fixed bug: missing embeddings on single scrape\n\n# 2.14.4\n- Fixed bug: missing embeddings on content coming from auto-reindex scheduler\n\n# 2.14.3\n- Fixed bug: first message not sent from default chatbot. Refactored availableAgentsCount handling in requestService to ensure accurate snapshot updates during request creation.\n\n# 2.14.2\n- Updated updateRequestSnapshotQueued.js to update the snapshot only\n\n# 2.14.1\n- Fixed missing snapshot parameter in event request\n- Updated twilio-connector to 0.1.28\n\n# 2.14.0\n- Refactoring of route() and create() method on RequestService in order to improve performance for firt message\n- Fix bug: missing embedding apikey on add single content\n\n# 2.13.51 - aborted\n- Refactoring of route() and create() method on RequestService in order to improve performance for first message\n- Updated tests\n\n# 2.13.50\n- Updated kb route to support embeddings\n- Updated tybot-connector to 2.0.41\n- Updated vxml-connector to 0.1.89\n\n# 2.13.49\n- Updated tybot-connector to 2.0.41\n\n# 2.13.48\n- Updated twilio-connector to 0.1.26\n\n# 2.13.46\n- Updated whatsapp-connector to 1.0.18\n\n# 2.13.45\n- Updated tybot-connector to 2.0.40\n\n# 2.13.44 - aborted\n- Updated tybot-connector to 2.0.39\n\n# 2.13.43\n- Updated requestService to improve fully abandoned management\n\n# 2.13.42\n- Updated whatsapp-connector to 1.0.17\n\n# 2.13.40\n- Changed minimum role access to agent for route /:project_id/logs\n\n# 2.13.39\n- Fixed bug on /qa with reranking\n\n# 2.13.38\n- Updated tybot-connector to 2.0.38\n- Updated /qa to support reranking with hybrid namespaces\n- Updated default AI contexts\n\n# 2.13.37\n- Improved abandoned requests management\n\n# 2.13.36\n- Fixed /logs/whatsapp/:phone_number endpoint to logs route\n\n# 2.13.35\n- Updated whatsapp-connector to 1.0.15\n\n# 2.13.34\n- Updated whatsapp-connector to 1.0.14\n- Added /logs/whatsapp/:phone_number endpoint to logs route\n\n# 2.13.33\n- Fix bug on create chatbot from public template\n\n# 2.13.31\n- Added default context for general LLM\n- Updated tybot-connector to 2.0.35\n\n# 2.13.29\n- Minor improvements\n\n# 2.13.27\n- Added rate manager for webhook call\n- Increased json body limit for /webhook endpoint\n\n# 2.13.26\n- Fixed bug: LLM preview not working\n\n# 2.13.24\n- Code improvements\n\n# 2.13.23\n- Updated whatsapp-connector to 1.0.9\n- Updated messenger-connector to 0.1.28\n- Code improvements\n\n# 2.13.22\n- Updated whatsapp-connector to 1.0.9\n\n# 2.13.21\n- Updated whatsapp-connector to 1.0.8\n\n# 2.13.20\n- Minor bug fix on email channel\n\n# 2.13.19\n- Improved: llm preview to support openai models\n- Updated: 2.0.30\n- Fix tokens usage for llm preview\n\n# 2.13.17\n- Updated: whatsapp-connector to 1.0.7\n\n# 2.13.16\n- Updated: whatsapp-connector to 1.0.6\n\n# 2.13.15\n- Bug fix: email quota reached sent to project admin\n\n# 2.13.14\n- Bug fix: slug duplicated error on publish chatbot\n- Updated: whatsapp-connector to 1.0.5\n\n# 2.13.12\n- Updated: whatsapp-connector to 1.0.4\n\n# 2.13.11\n- Updated: whatsapp-jobworker to 0.0.13\n\n# 2.13.10\n- Updated: whatsapp-connector to 1.0.4-rc4\n\n# 2.13.9\n- Updated: whatsapp-connector to 1.0.4-rc3\n\n# 2.13.8\n- Updated: whatsapp-connector to 1.0.4-rc2\n\n# 2.13.7\n- Updated: tybot-connector to 2.0.27\n\n# 2.13.6\n- Updated: whatsapp-connector to 1.0.3\n\n# 2.13.5\n- Updated: whatsapp-connector to 1.0.2\n\n# 2.13.4\n- Updated: whatsapp-connector to 1.0.1\n\n# 2.13.2\n- Bug fix: wrong endpoint for hybrid qa\n\n# 2.13.0\n- Updated: whatsapp module listener\n- Updated: whatsapp-connector to 1.0.0\n- Updated: tybot-connector to 2.0.26\n- Updated: vxml-connector to 0.1.77\n\n# 2.11.12 (deprecated)\n- Updated: whatsapp-connector to 0.1.87\n\n# 2.11.11 (deprecated)\n- Updated: whatsapp module listener\n- Updated: whatsapp-connector to 0.1.86\n\n# 2.11.10 (deprecated)\n- Updated: whatsapp module listener\n- Updated: whatsapp-connector to 0.1.85\n- Updated: tybot-connector to 2.0.26\n- Updated: vxml-connector to 0.1.77\n\n# 2.11.9\n- Changed: oauth2 endpoints in auth route\n- Updated: improved project users management (soft delete)\n\n# 2.11.8\n- Added: migration file to migrate namespace engine\n\n# 2.11.7\n- Hotfix: solved bug on findProjectUsersAllAndAvailableWithOperatingHours_group on searching group\n\n# 2.11.6\n- Added: possibility to enable/disable groups\n- Updated: delete project user endpoint to logical delete\n- Updated: tybot-connector to 2.0.23\n- Updated: get all namespace adding counter\n- Added: attributes field in groups model\n\n# 2.11.5\n- Added: log in requestService\n\n# 2.11.4\n- Added: authentication via Keycloak\n- Added: project settings fields for allowed_urls, allowed_urls_list, allow_send_emoji\n\n# 2.10.104\n- Update: standard/hybrid namespace management\n- Update: tybot-connector to 2.0.21\n\n# 2.10.102\n- Update: substituted encode with DOMPurify.sanitize for direct email\n\n# 2.10.101\n- Update: messenger-connector to 0.1.27\n- Update: multi-worker to 0.3.3\n- Added: endpoints for unanswered questions\n- Update: endppoints to static logs\n- Improved knowledge base import/export\n\n# 2.10.100\n- Update: removed verbose logs\n\n# 2.10.99\n- Update: messenger-connector to 0.1.24\n\n# 2.10.98\n- Update: whatsapp-connector to 0.1.84\n\n# 2.10.97\n- Added: support for hybrid search\n- Added: support for chunks_only option\n- Added: support for native logs\n- Bug fix: unexpceted indexing run of all urls in the project\n- Added: BASE_FILE_URL to twilio voice listener\n\n# 2.10.95 (Aborted)\n- Added: support for hybrid search\n- Added: support for chunks_only option\n- Added: support for native logs\n\n# 2.10.94\n- Update: fix uncaughtException in tagSchedule method\n\n# 2.10.93\n- Added: endpoints to import/export namespaces\n\n# 2.10.92\n- Updated voice-twilio-connector to 0.1.22\n- Bug fix: tags scheduling\n\n# 2.10.91\n- Updated: /qa service to return chunks (debug: true)\n- Updated: tybot-connector to 2.0.15\n\n# 2.10.89\n- Added: modified field in faq_kb model\n- Added: chatbot template refactoring\n- Added: clearing logic for chatbot\n- Added: ttl when a chatbot is deleted\n- Added: flow logs apis\n- Added: webhooks apis\n- Added: user_phone field in user model\n- Updated tybot-connector to 2.0.14\n\n# 2.10.88 (aborted)\n- Added: modified field in faq_kb model\n- Added: chatbot template refactoring\n- Added: clearing logic for chatbot\n- Added: ttl when a chatbot is deleted\n- Added: flow logs apis\n- Added: webhooks apis\n- Added: user_phone field in user model\n\n# 2.10.87\n- Updated voice-twilio-connector to 0.1.18\n\n# 2.10.86\n- Updated voice-twilio-connector to 0.1.16\n\n# 2.10.85\n- Updated: default system context for gpt-4.1 models\n\n# 2.10.84\n- Added: missing default contexts for gpt-4.1 models\n- Updated tybot-connector to 2.0.10\n\n# 2.10.83\n- Fix bug: missing moment.js import on /requests/me\n\n# 2.10.82\n- Updated whatsapp-connector to 0.1.83\n\n# 2.10.81\n- Fix bug on webhook import and fork (wrong blank template)\n- Updated whatsapp-connector to 0.1.82\n\n# 2.10.80\n- Updated tybot-connector to 2.0.9\n\n# 2.10.79\n- Updated tybot-connector to 2.0.8\n\n# 2.10.78\n- Updated vxml-connector to 0.1.76\n- Updated vxml-connector listener\n- Fix bug on message transcript template (brandLogo)\n\n# 2.10.77\n- Updated whatsapp-jobworker to 0.0.12\n- Fix logs and log levels\n\n# 2.10.76\n- Added support for ollama in llm preview\n\n# 2.10.75\n- Updated tybot-connector to 2.0.7\n\n# 2.10.74\n- Updated tybot-connector to 2.0.6\n\n# 2.10.73\n- Updated tybot-connector to 2.0.4\n\n# 2.10.72\n- Updated tybot-connector to 2.0.3\n\n# 2.10.71\n- Updated tybot-connector to 2.0.2\n- Bug fix: chatbot loosing slug after import/fork\n- Added: white labelling on messages template (transcript)\n\n# 2.10.70 /* aborted */\n- Updated tybot-connector to 2.0.0\n- Bug fix: chatbot loosing slug after import/fork\n- Added: white labelling on messages template (transcript)\n\n# 2.10.69\n- Updated tiledesk-apps to 1.0.28\n\n# 2.10.68\n- Updated tybot-connector to 0.4.2\n\n# 2.10.67\n- Added route for webhooks\n- Added endpoint to run webhook\n- Added chatbot subtypes\n- Updated tybot-connector to 0.4.0\n\n# 2.10.66\n- Updated QuoteService with new plans\n\n# 2.10.65\n- fix issue con /rating called by chatbot\n\n# 2.10.64\n- updated tybot-connector to 0.3.4\n- updated whatsapp-connector to 0.1.81\n- fix bug: not existing err variable on requestService \n\n# 2.10.63\n- updated tybot-connector to 0.3.3\n- updated multi-worker to 0.1.20\n\n# 2.10.62\n- bug fix: added missing formData import in aiService\n\n# 2.10.61\n- updated tybot-connector to 0.3.2\n- updated whatsapp-connector to 0.1.78\n- minor fix\n\n# 2.10.59\n- updated tybot-connector to 0.2.152\n- restored old default system contexts\n\n# 2.10.58\n- updated tybot-connector to 0.2.150\n- fix issue on reconnect to rabbit queue (kb indexing)\n- updated multi-worker to 0.1.19\n- fix issue on TILEBOT_ENDPOINT undefined\n- added endpoint for llm preview\n- updated default contexts for gpt-4o and gpt-4o-mini\n\n# 2.10.56\n- bug fix: wrong tilebot_endpoint declaration\n\n# 2.10.54\n- updated multi-worker to 0.1.19\n- updated queueManagerClass to improves queue reconnect system\n\n# 2.10.53\n- added voice quota duration\n\n# 2.10.52\n- updated tybot-connector to 0.2.148\n- updated faqRoute /get endpoint with restricted mode\n\n# 2.10.51\n- updated /replace endpoint adding trashed: false inside query\n\n# 2.10.50\n- updated tybot-connector to 0.2.147\n- removed logs\n- fix /department/operators bug (Circular Dependency on JSON Stringify)\n\n# 2.10.49\n- changed TILEBOT_ENDPOINT\n\n# 2.10.48\n- updated tybot-connector to 0.2.146\n\n# 2.10.44\n- updated tybot-connector to 0.2.141\n\n# 2.10.43\n- changed index on fulltext in faq model adding id_faq_kb\n\n# 2.10.42\n- updated tybot-connector to 0.2.140 \n\n# 2.10.41\n- added agents_available field in faq_kb and faq models\n\n# 2.10.40\n- bug fix: empty string for slug and index issue\n\n# 2.10.39\n- updated whatsapp-connector to 0.1.76\n- updated tybot-connector to 0.2.139\n- added chatbot slug in faq_kb model\n- added /replace endpoint in request \n\n# 2.10.38\n- updated whatsapp-worker to 0.1.11\n- added index to request model\n\n# 2.10.36\n- updated tybot-connector to 0.2.138\n\n# 2.10.35\n- added tiledesk-multi-worker to 0.1.6\n\n# 2.10.34\n- added add tags endpoint\n- added tags analytics endpoint\n- added tiledesk-multi-worker to 0.1.5\n- updated tybot-connector to 0.2.134\n- updated vxml-connector to 0.1.67\n- updated voice-twilio-connector to 0.1.12\n- updated sms-connector to 0.1.11\n- fix kb source with citations issue (from dashboard)\n\n# 2.10.33\n- Bug fix: conflicts between faqs and urls with same source of different namespaces\n\n# 2.10.32\n- Externalized PINECONE_TYPE variable for kb engine\n\n# 2.10.31\n- updated twilio-voice-connector to 0.1.11\n\n# 2.10.30\n- updated tybot-connector to 0.2.134\n\n# 2.10.29\n- Minor improvements external channels\n\n# 2.10.28\n- removed duplicated index on Request model\n\n# 2.10.27\n- updated tybot-connector to 0.2.133\n- updated vxml-connector to 0.2.65\n- updated EmailService methods\n\n# 2.10.26\n- Added missing catch blocks\n\n# 2.10.25\n- updated tybot-connector to 0.2.132\n- updated twilio-voice-connector to 0.1.10\n\n# 2.10.24\n- updated tybot-connector to 0.2.132-rc1\n- updated vxml-connector to 0.2.64\n- updated twilio-voice--connector to 0.1.9\n\n# 2.10.23\n- Removed logs\n\n# 2.10.22\n- Test version (Alert)\n\n# 2.10.21\n- Added catch blocks where necessary (improves error management)\n\n# 2.10.20\n- updated whatsapp-connector to 0.1.75\n\n# 2.10.19\n- updated whatsapp-connector to 0.1.74\n- updated whatsapp-worker to 0.1.10\n- updated voice-twilio-connector to 0.1.8\n- updated get transactions endpoint (only broadcast automations are returned)\n\n# 2.10.18\n- updated messenger-connector 0.1.23\n- updated voice-twilio module \n\n# 2.10.17\n- changed bodyParser.urlencoded extended to TRUE\n- updated tybot-connector to 0.2.130\n- added twilio voice module\n- updated messenger-connector 0.1.22\n\n# 2.10.16 - abort\n- changed bodyParser.urlencoded extended to TRUE\n- updated tybot-connector to 0.2.130\n- added twilio voice module\n- updated messenger-connector 0.1.22\n\n# 2.10.15\n- Readded event on fully_abandoned request\n\n# 2.10.14\n- Updated tybot-connector to 0.2.127\n- Restored request.js file\n\n# 2.10.13\n- Updated tybot-connector to 0.2.127\n- Added event on request fully abandoned\n\n# 2.10.12\n- Updated tiledesk-chatbot-template to 0.1.3\n- Added check on valid mongo id\n- Removed logs\n\n# 2.10.11\n- Updated tybot-connector to 0.2.118\n- Removed logs\n- Removed $clusterTime from add kb response\n\n# 2.10.9\n- Removed logs\n- Updated tiledesk-apps to 1.0.26\n\n# 2.10.8\n- Fix bug closing conversation by chatbot\n- Fix bug typo error on import chatbot as json\n\n# 2.10.7\n- Update: removed regex in signup and changepsw (temp)\n- Update: fire request.close event even if the request is already closed\n\n# 2.10.6\n- Update: return more chatbot info with restricted_mode\n\n# 2.10.5\n- Fix bug on access chatbot route rules\n- Fix bug update chatbot avatar\n\n# 2.10.4\n- Removed temporary conversation from default query to get all requests\n- Removed draft conversation from requests count\n- Improved security\n\n# 2.10.3\n- Added support for formatType in openai completions\n\n# 2.10.2\n- Updated tybot-connector to 0.2.113\n\n# 2.10.1\n- Bugfix: try to read fully_abandoned of request without attributes\n\n# 2.10.0\n- Added support for engine to namespace\n- Added support for advanced context to kb\n- Added support for scrape types to kb\n- Updated tybot-connector to 0.2.112\n- Updated vxml-connector to 0.1.54\n- Updated widget.json translation file\n\n# 2.9.32\n- Added voice filters in get requests\n- Added endpoint to get all projects\n- Updated vxml-connector to 0.1.49\n\n# 2.9.31\n- Improved conversations queues management\n- Added conversation status 150 (ABANDONED)\n\n# 2.9.30\n- Restore Voice and SMS modules\n\n# 2.9.29\n- Fixed bug: try to update non existant project user (bot)\n\n# 2.9.28\n- Updated number_assigned_request count logic (removed incr/decr)\n\n# 2.9.27\n- Updated tybot-connector to 0.2.107\n- Improved quotas slots\n- Improved requests quota count (temporary conversation will no longer counted)\n- Fixed bug: savedFaq is not defined in /importjson\n\n# 2.9.26\n- Updated tybot-connector to 0.2.105\n- Added route for faqs csv file uploading on /kb\n\n# 2.9.25\n- Updated vxml-connector to 0.1.44\n\n# 2.9.24\n- Updated tybot-connector to 0.2.104\n\n# 2.9.23\n- Updated whatsapp-connector to 0.1.73\n\n# 2.9.22\n- Added support for chatbots_attribute_hidden parameter in project update\n- Exclude from count voice conversation\n- Updated: vxml-connector to 0.1.43\n\n# 2.9.21\n- Updated: tybot-connector to 0.2.96\n\n# 2.9.20\n- Fixed bug: delete namespace doesn't work properly (wrong namespace)\n\n# 2.9.19\n- Update: force lastRequestsLimit to be a number\n\n# 2.9.18\n- Added: /requests/count endpoint\n- Updated: sms-connector to 0.1.10\n- Updated: vxml-connector to 0.1.39\n\n# 2.9.16\n- Update: change automatically the content status on re-index\n\n# 2.9.15\n- Updated jobs-worker-queue-manager\n\n# 2.9.14\n- Added replace option in chatbot import json\n- Added field project_user, fullname, email and assigned requests to what /users/availables returns \n\n# 2.9.13\n- Updated tybot-connector to 0.2.95\n- Improved /users/availables endpoint with department and smart assignment check\n\n# 2.9.12\n- Added support for Team plan\n\n# 2.9.11\n- Bug fix quotas reset after stripe subscription update\n\n# 2.9.10\n- Update tybot-connector to 0.2.94\n\n# 2.9.9\n- Return draft conversations (for non real time monitor)\n\n# 2.9.8\n- Added model contexts fro kb preview \n- Update tybot-connector to 0.2.93\n\n# 2.9.7\n- Fix bug: draft conversation was shown in monitor\n- Update tybot-connector to 0.2.92\n\n# 2.9.6\n- Fix bug: wrong timzone in startTime and andTime in operating hours service\n\n# 2.9.5\n- Added raw option in /users/availables\n\n# 2.9.4\n- Update tybot-connector to 0.2.86\n- Added time-slots management\n\n# 2.9.3\n- Added log for AMQP error in closeOnErr\n\n# 2.9.2\n- Updated tybot-connector to 0.2.84\n\n# 2.9.1\n- Update sms-connector to 0.1.7\n- Update voice-connector to 0.1.38\n- Change send email quota checkpoint template key\n\n# 2.9.0\n- Added Twilio SMS route\n- Added VXML Voice route (hidden)\n\n# 2.8.8\n- Changed default prompt for kb q&a\n\n# 2.8.7\n- Update project update endpoint with agent chats only function \n\n# 2.8.6\n- Bug fix: token count was incremented in action preview and kb preview even with a private key in integration\n\n# 2.8.5\n- Restored sandbox limits for free trial plan\n\n# 2.8.4\n- Updated tybot-connector to 0.2.83\n\n# 2.8.3\n- Added advanced_context support\n- Updated default preview settings\n\n# 2.8.2\n- Updated default preview settings\n- Added scrape_type in /scrape/single for fix indexing on retrain\n\n# 2.8.1\n- Added trashed=false in chatbot namespace query\n- Return empty array if no chatbots are using the namespace\n- Enhanced kb context\n- Added enpoint to retrieve all content's chunks\n- Added limit on namespaces\n- Restore beenInvitedNewUser email\n\n# 2.8.0\n- Enable quotas for conversations, tokens, and direct email.\n- Added namespaces to knowledge base\n- Updated tybot-connector to 0.2.82\n- Externalized models multipliers\n\n# 2.7.26\n- Updated tybot-connector to 0.2.72\n\n# 2.7.25\n- Updated tybot-connector to 0.2.70\n- Changed auth role strategy in groups route and project_user endpoint\n\n# 2.7.24\n- Updated whatsapp-jobworker to 0.0.8\n- Updated knowledge base route\n\n# 2.7.23\n- Updated tybot-connector to 0.2.69\n\n# 2.7.22\n- Updated whatsapp-connector to 0.1.72\n\n# 2.7.21\n- Updated whatsapp-connector to 0.1.71\n- Updated telegram-connector to 0.1.14\n\n# 2.7.20\n- Updated tybot-connector to 0.2.67\n\n# 2.7.18\n- Updated tybot-connector to 0.2.65\n\n# 2.7.17\n- Updated whatsapp-connector to 0.1.70\n\n# 2.7.16\n- Updated tybot-connector to 0.2.63\n\n# 2.7.15\n- Updated whatsapp-connector to 0.1.69\n\n# 2.7.14\n- Updated whatsapp-connector to 0.1.68\n\n# 2.7.13\n- Updated whatsapp-connector to 0.1.67\n\n# 2.7.12\n- Updated tybot-connector to 0.2.62\n\n# 2.7.11\n- Updated telegram-connector to 0.2.12\n- Updated messenger-connector to 0.2.21\n\n# 2.7.10\n- Fix get email templates\n\n# 2.7.9\n- Updated whatsapp-connector to 0.2.66\n- Updated telegram-connector to 0.2.11\n- Updated messenger-connector to 0.2.20\n- Fixed bug: duplicated faq with intentid\n\n# 2.7.8\n- Updated tybot-connector to 0.2.61\n\n# 2.7.7\n- Fix update user verifiedEmail with signup from Admin\n- Updated WhatsApp listener to reduce mongodb connections\n- Fix duplicated intents\n\n# 2.7.6\n- Fix auth with admin token\n\n# 2.7.5\n- Added support for bot PRIVATE and PUB ket in auth.js\n\n# 2.7.4\n- Bug fix '\\start' in rulesTrigger\n- SSO fix\n- Updated tybot-connector to 0.2.60\n- Updated project profile call whit super admin token\n- Updated user signup with super admin token\n\n# 2.7.3\n- Updated project profile call\n- Updated tybot-connector to 0.2.59\n\n# 2.7.2\n- Improved QuoteManager with kbs and chatbots (disabled)\n- Improved QuoteManager with AI multipliers\n\n# 2.7.1\n- Updated widget.json translation file\n- Improved widget White Label \n\n# 2.7.0\n- Lead update queued\n- Updated tybot-connector to 0.2.57\n- Updated kb route\n- Added trainer job worker\n\n# 2.5.3\n- Updated whatsapp-connector to 0.1.64\n\n# 2.5.2\n- Updated messenger-connector to 0.1.18\n- Bug fix: kbs createdAt wrongly generated\n- Added advanced search for kbs\n\n# 2.5.1\n- Bug fix: reset busy status for agents when smart assignment is enabled\n- Added possibility to delete chat21 conversation \n- Updated tybot-connector to 0.2.56\n\n# 2.5.0\n- Added new pricing modules\n- Added integrations route\n- Added new kb route\n- Added mqttTest module\n\n# 2.4.103\n- Added new pricing modules\n- Added integrations route\n- Added new kb route\n- Added mqttTest module\n\n# 2.4.102\n- Updated whatsapp-connector to 0.1.63\n- Updated messenger-connector to 0.1.17\n- Added quote management\n\n# 2.4.101\n- Added new route for knowledge base\n- Bug fix: conflicts with old knowledge base\n\n# 2.4.100\n- Updated tybot-connector to 0.2.50\n- Added new route for knowledge base\n\n# 2.4.99\n- Updated whatsapp-connector to 0.1.62\n- Updated messenger-connector to 0.1.16\n- Bug fix: globals are not exported when a chatbot is published\n\n# 2.4.98\n- Updated whatsapp-connector to 0.1.61\n\n# 2.4.97\n- Updated whatsapp-connector to 0.1.61\n\n# 2.4.96\n- Updated tybot-connector to 0.2.49\n\n# 2.4.95\n- Updated tybot-connector to 0.2.48\n\n# 2.4.94\n- Updated WIDGET_LOCATION usage\n\n# 2.4.92\n- Updated messenger-connector to 0.1.14\n\n# 2.4.91\n- Bug fix: globals will no longer exported in chatbot export\n\n# 2.4.90\n- Updated tybot-connector to 0.2.45\n\n# 2.4.89\n- Updated whatsapp-connector to 0.1.60\n\n# 2.4.88\n- Added chatbot templates and community in pubmodules\n\n# 2.4.87\n- Updated tybot-connector to 0.2.43\n- Updated whatsapp-connector to 0.1.59\n\n# 2.4.86\n- Updated tybot-connector to 0.2.41\n\n# 2.4.85\n- Updated tybot-connector to 0.2.40\n\n# 2.4.84\n- Updated tybot-connector to 0.2.38\n- Updated whatsapp-connector to 0.1.58\n\n# 2.4.83\n- Improved whatsapp log services\n- Updated whatsapp-connector to 0.1.57\n- Updated whatsapp-jobworker to 0.0.7\n\n# 2.4.82\n- Added whatsapp log services\n\n# 2.4.81\n- update whatsapp-connector to 0.1.56\n\n# 2.4.79\n- update whatsapp-connector to 0.1.55\n- update whatsapp-jobworker to 0.0.4\n- added support for whatsapp broadcast queue\n\n# 2.4.78\n- update whatsapp-connector to 0.1.53\n- update messenger-connector to 0.1.13\n- update telegram-connector to 0.1.10\n\n# 2.4.77\n- update tybot-connector to 0.2.26\n\n# 2.4.76\n- update tybot-connector to 0.2.25\n- update redirectToDesktop email for new onboarding\n\n# 2.4.74\n- update tiledesk-messenger-connector to 0.1.12\n\n# 2.4.73\n- Fix KB Settings bugs\n\n# 2.4.72\n- update tiledesk-messenger-connector to 0.1.11\n\n# 2.4.71\n- update tiledesk-messenger-connector to 0.1.10\n\n# 2.4.70\n- campaign direct refactoring with job worker\n- segment filter fix for lead\n\n# 2.4.69\n- update tiledesk-tybot-connector to 0.2.15\n\n# 2.4.68\n- update tiledesk-tybot-connector to 0.2.11\n\n# 2.4.67\n- update tiledesk-tybot-connector to 0.2.9\n\n# 2.4.63\n- downgrade tiledesk-tybot-connector to 0.1.97\n\n# 2.4.62\n- improved kbsettings endpoints for qa, scrape and check status\n- update tiledesk-whatsapp-connector to 0.1.52\n- update tiledesk-telegram-connector to 0.1.8\n\n# 2.4.59\n- update tiledesk-whatsapp-connector to 0.1.52\n- update tiledesk-telegram-connector to 0.1.8\n\n# 2.4.57\n- added telegram module\n- update tiledesk-apps to 0.1.17\n- update tiledesk-telegram-connector to 0.1.7\n\n# 2.4.55\n- updated tybot-connector to 0.1.97\n\n# 2.4.43\n- updated tybot-connector to 0.1.96\n- added segment module\n\n# 2.4.42\n- createIfNotExistsWithLeadId now update the lead email if jwt email changes\n\n# 2.4.41\n- Whatsapp updates\n\n# 2.4.40\n- botSubscriptionNotifier and botEvent queued disabled\n- added lead.create to the queue\n- queued scheduler (close chat)\n- added chat21 channel, cache, rules \n\n# 2.4.39\n- Queued botSubscriptionNotifier\n- added lead.create to the queue\n- @tiledesk/tiledesk-tybot-connector: 0.1.89\n- jobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events\n\n# 2.4.38\n- str fix\n\n# 2.4.37\n- added replyto to email endpoint\n- tiledesk/tiledesk-tybot-connector: 0.1.88\n- added lead.create to the queue\n- added subscriptionNotifiedQueued for the most common events (message.create, request.create, request.update, request.close, lead.create, project_user.update)\n\n\n# 2.4.36\n- Google last name fix with empty string\n\n# 2.4.35\n- tiledesk/tiledesk-tybot-connector: 0.1.87\n\n# 2.4.34 -> ERRORE PAGAMENTO\n- tiledesk/tiledesk-tybot-connector:0.1.83\n- stateless as default for Google Strategy\n- added redis session for google auth. To enable it put ENABLE_REDIS_SESSION to true otherwise standard passport session is used\n\n# 2.4.33\n- \"@tiledesk/tiledesk-tybot-connector\": \"^0.1.86\"\n- stateless true for Google Strategy\n\n# 2.4.32\n- close chat set timeout for performance\n\n# 2.4.31\n- close chat set timeout for performance\n\n# 2.4.30\n- close chat set timeout for performance\n\n# 2.4.29\n- @tiledesk/tiledesk-whatsapp-connector 0.1.50\n\n# 2.4.28\n- smartAssignment default value condition for channels\n\n# 2.4.27\n- tiledesk/tiledesk-tybot-connector:0.1.83\n\n# 2.4.26\n- filter by priority\n- bugfix intent invalidation by intent id\n\n# 2.4.25\n- \"@tiledesk/tiledesk-tybot-connector\": \"^0.1.81\"\n- bugfix intent invalidation by intent_display_name\n\n# 2.4.24\n- Wildcard invalidate bugfix for intents, subscriptions and triggers\n\n# 2.4.23\n- waiting time chance invalidation fix\n- delete redis fix\n\n# 2.4.22\n- bugfix changed del method from cachemon to native redis\n\n# 2.4.21\n- changed del method from cachemon to native redis\n\n# 2.4.20\n- removed unused redis delete with wildcard\n\n# 2.4.19\n- Added QUEUE_NAME env parameter\n\n# 2.4.18\n- logfix\n\n# 2.4.17\n- logfix\n- post and .patch for /properties are equals\n- bugfix added invalidatRequestSimple for waiting time issue\n- added QUEUE_EXCHANGE_TOPIC env parameter\n- email template changes\n\n\n# 2.4.16\n- env for durable and persistent queue\n\n# 2.4.15\n- persistent false\n\n\n# 2.4.14 -> PROD\n- added request_channel attribute to chat21 messages\n- moved propertiies to agent role\n- logfix\n- \"@tiledesk/tiledesk-tybot-connector\": \"^0.1.80\"\n\n# 2.4.13\n- \"@tiledesk/tiledesk-tybot-connector\": \"^0.1.79\"\n\n# 2.4.12\n- logfix\n- givanni fix\n\n# 2.4.11\n- whatsapp connector aggiornata alla 0.1.48\n- aggiunti campi title, certifiedTags e short_description nel modello faq_kb\n- gestiti i campi title, certifiedTags e short_description in update faq_kb\n\n# 2.4.10\n- email template fix\n\n# 2.4.9\n- added trained field to bot entity\n- added training endpoint\n- skip has role verification for admin@td user\n- bot websocket realtime endpoint\n- added properties to lead model and patch endpoint\n- @tiledesk/tiledesk-whatsapp-connector 0.1.46\n\n# 2.4.8\n- added webp image\n- added description to user entity\n\n# 2.4.7\n\n# 2.4.6\n- Added forcing message to bot.calling trigger action\n\n# 2.4.5\n- Custom subject for direct email. Also supported with trigger subject configuration (from DB). See Custom OCF trigger Open Ticket email\n\n# 2.4.3\n- tiledesk-ent/tiledesk-server-payments: 1.1.12 with node 16.20\n# 2.4.2\n- tiledesk-whatsapp-connector to 0.1.45\n- bot.calling trigger added\n- Endpoint to generate a jwt token for a chatbot\n- tiledesk-ent/tiledesk-server-payments: 1.1.11 with node 16\n\n# 2.4.1\n- tiledesk/tiledesk-tybot-connector\": \"^0.1.77\n- node 16 support for package.json and dockers files\n\n# 2.3.132\n- Added channel name to message attributes for chat21 engine\n\n# 2.3.131\n- update tiledesk-whatsapp-connector to 0.1.44\n- update tiledesk-messenger-connector to 0.1.9\n\n# 2.3.130\n- cache invalidation for docker image\n\n# 2.3.129\n- ocf email fix\n\n# 2.3.128\n- email subject customization on project settings\n\n# 2.3.127\n- Added Google OAuth Strategy\n- tiledesk/tiledesk-tybot-connector\": \"^0.1.76\n\n\n# 2.3.126\n- removed secret from faq_kb cache before caching\n- removed description and attributes from chabot subscription notifier to reduce the payload size\n\n# 2.3.125\n-  @tiledesk/tiledesk-tybot-connector@0.1.74\n\n# 2.3.124\n-  tiledesk/tiledesk-messenger-connector 0.1.8\n\n# 2.3.123\n-  tiledesk/tiledesk-messenger-connector 0.1.7\n\n# 2.3.122\n- new stripe\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.73\n\n# 2.3.121\n- trial period to 14 days\n- tiledesk-ent/tiledesk-server-payments: 1.1.9 with new stripe plan\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.72\n\n# 2.3.120\n- ocf fix\n\n# 2.3.119\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.71\n\n# 2.3.118\n-  tiledesk/tiledesk-messenger-connector 0.1.3\n\n# 2.3.117\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.70\n\n# 2.3.116\n- tiledesk/tiledesk-dialogflow-connector\": \"^1.8.4\",\n\n# 2.3.115\n- github action fix\n\n# 2.3.113\n- tiledesk-ent/tiledesk-server-payments: 1.1.7 with new stripe plan\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.68\n- tiledesk-whatsapp-connector 0.1.41\n\n# 2.3.112\n- fb messenger fix\n\n# 2.3.111\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.67\n- facebook messenger plugin added \n\n# 2.3.110\n- websocket fix ATTENTION change value by reference\n- Bugfix: Cannot read property 'toString' of undefined at /usr/src/app/services/requestService.js:404\n- Updated tiledesk/tiledesk-whatsapp-connector 0.1.40\n- Implmented request /agent endpoint (also support request with empty deparment generated by direct agent/bot assignment with participants field)\n- fixed bug: fields webhook_enabled and webhook_url was ignored on “publish”\n\n# 2.3.109\n- Added support gif mimetype for image endpoint \n\n# 2.3.108\n- BugFix Chat21 event emitter listener changes request attributes values by reference\n- Restored populate for message endpoint\n- Test attributes send message\n\n# 2.3.107\n- bugfix execPopulate message endpoint wrong attributes\n- Dependency updated tiledesk-whatsapp-connector 0.1.39\n\n\n# 2.3.106\n@tiledesk/tiledesk-tybot-connector 0.1.63\n{{dateFormat request.createdAt \\\"DD/MM/YYYY HH:mm:ss\\\"}}\n# 2.3.105\n- tiledesk/tiledesk-apps: 1.0.14\n\n# 2.3.104\n- apps module auth fix\n\n# 2.3.103 \n- Fix image not found bug\n\n# 2.3.102\n- Fix image not found bug\n\n# 2.3.101\n- created property entity and endpoint\n- JSON_BODY_LIMIT env variable fix\n\n# 2.3.100\n- added filter by tags for lead endpoint\n\n# 2.3.99\n- tags field changed to flat object\n\n# 2.3.98\n- Ocf fix\n\n# 2.3.97\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.62\n\n# 2.3.96\n- image endpoint set content type and length headers\n\n# 2.3.95\n- Bugfix when a conversation has a first_text with \\agent\n\n# 2.3.94\n- log fix\n\n# 2.3.93\n- subscriptionNotifier secret fix\n\n# 2.3.92\n- Apps module fix for public secret key\n\n# 2.3.91\n- subscription webhook fix for global webhook secret creation for token\n- added jsonRequest field to subscriptionLog to save request body payload\n\n# 2.3.90\n- Dependency updated tiledesk-tybot-connector 0.1.59\n\n# 2.3.89 \n- added as_attachment query parameter to images endpoint to download the images\n\n# 2.3.88\n- added score, publishedBy and publishedAt to bot model \n\n# 2.3.87\n- Geo Service fix with queue enabled\n\n# 2.3.86 \n- Geo Service fix with queue enabled\n\n# 2.3.85\n- Dependency updated tiledesk-whatsapp-connector 0.1.33\n\n# 2.3.84 \n- Dependency updated tiledesk-tybot-connector 0.1.58\n- Dependency updated tiledesk/tiledesk-kaleyra-proxy 0.1.7\n\n# 2.3.83\n- Dependency updated tiledesk-tybot-connector 0.1.56\n- Dependency updated tiledesk-whatsapp-connector 0.1.32\n\n# 2.3.82\n- Enable current chabot when &td_draft=true query parameter is passed in the url\n\n# 2.3.81\n- Added publish method to the chatbot endpoint and fix for SubscriptionNotifier\n- Added request_status and preflight body parameters to send (post) message endpoint\n\n# 2.3.80\n- Updated dep tiledesk/tiledesk-whatsapp-connector to 0.1.31\n\n# 2.3.79\n- Env fix\n\n# 2.3.78 \n- Updated dep tiledesk/tiledesk-whatsapp-connector to 0.1.30\n- Added FaqSchema.index({ id_project: 1, id_faq_kb: 1, intent_id: 1 }, { unique: true });\n- Added filter by public and certified to the bot endpoint\n- Added cache invalidation for intent edit and delete\n- Added score field to the bot model\n\n# 2.3.77 \n- Updated tiledesk/tiledesk-whatsapp-connector dependency to 0.1.24\n- Added KALEYRA_API_URL environment variable\n- Now GRAPH_URL environment variable is optional \n- Added bot cache invalidation for new radis key without project for tilebot\n- Added chatbot fulltext endpoint \n\n# 2.3.76\n- Adedd tag to bot model\n- \\\\n fix for public private key\n- Chatbot invalidation fix when created\n- @tiledesk/tiledesk-tybot-connector@0.1.53\n\n# 2.3.75\n- @tiledesk/tiledesk-tybot-connector@0.1.51\n\n# 2.3.74\n- Added cache for bot \n- @tiledesk/tiledesk-tybot-connector@0.1.50\n- Added cache for user \n\n# 2.3.73\n- Removed unused mongoose-auto-increment dependency\n- Removed version versions files\n- Added attributes field to the project model and relative patch endpoint \n- Message text validation only for the first message\n- Updated tiledesk/tiledesk-tybot-connector@0.1.46\n- Added an endpoint to patch the bot attributes\n- Added Faq_kbSchema index({certified: 1, public: 1});\n- Added ActivitySchema index({id_project: 1, createdAt: -1});\n- Changed from .remove to findByIdAndRemove for faq remove endpoint to fix event emitter payload\n- Updated tiledesk/tiledesk-tybot-connector@0.1.47\n- Added caching for /widgets endpoint with invalidations\n- Added bot rules to /widgets endpoint\n- Select false for resetpswrequestid field of user model\n- Added support for Public Private Key for JWT Auth with GLOBAL_SECRET_OR_PRIVATE_KEY and GLOBAL_SECRET_OR_PUB_KEY and GLOBAL_SECRET_ALGORITHM(default: HS256, RS256 for pub private key) environments variables. If these properties are configured Public/Private Key method is enabled otherwise if only GLOBAL_SECRET env varible is configured the shared secret method is used. \n- Updated tiledesk/tiledesk-tybot-connector@0.1.48\n- Removed “snapshot” from request payload for external chatbot\n- Added CHATBOT_TEMPLATES_API_URL env varible\n- Updated tiledesk/tiledesk-tybot-connector@0.1.49\n\n\n\n\n# 2.3.72\n- Added Kaleyra module\n- Updated tiledesk-apps to 1.0.12\n\n# 2.3.71.1 -> PROD v2\n- ocf email fix\n\n# 2.3.71 \n- Email service send email direct fit without request_id\n- Removed strong from transcript email template\n- Added for updateWaitingTimeByRequestId the field enable_populate\n- Added cache for message send endpoint \n- Added support to mention and group to mail endpoint\n- Now user role can use /users/search (for smtp)\n- Disable text validation for send message\n- Logo email fix\n\n# 2.3.70\n- Update tiledesk-tybot-connector to 0.1.42\n\n# 2.3.69\n- Update tiledesk-dialogflow-connector to 1.8.3\n\n# 2.3.68\n- Update tiledesk-dialogflow-connector to 1.8.2\n\n# 2.3.67\n- Tybot updated to 0.1.39\n\n# 2.3.66\n- ccEnabled fix\n\n# 2.3.65\n- Updated tiledesk-tybot-connector 0.1.38\n- chatbot and subscription can use send email endpoint\n- Added EMAIL_CC_ENABLED env variable to disable CCs email inboud notification\n\n# 2.3.64\n- log fix\n- Tybot updated to 0.1.37\n\n# 2.3.63\n- Tybot updated to 0.1.36\n- ChangeLog degli ultimi due commit:\n- added trainingService.js file \n- added certified, mainCategory and intentsEngine fields for faq_kb model \n- deleted new_bot_name for forked chatbot \n- updated faq and faq_kb tests\n- edit trainingService.js\n- add test for bot with intentsEngine (training) \n\n\n# 2.3.62\n- if (request.markModified) fix webhook in queue\n\n# 2.3.61\n- troubleshooting api added\n\n# 2.3.60\n- Tybot updated to 0.1.34\n\n# 2.3.59\n- Tybot updated to 0.1.32\n\n# 2.3.58\n- Tybot updated to 0.1.31\n\n# 2.3.57\n- Tybot updated to 0.1.30\n\n# 2.3.55 \n- Tybot updated to 0.1.28\n\n# 2.3.54\n- Added HIDE_CLOSE_REQUEST_ERRORS env variable\n- Added email.send trigger action \n- Added participants parameter for trigger action\n- Added status -1 for department model to hide department for dashboard and widget. It can be used for chatbot\n- Added template engine to send email trigger action\n\n# 2.3.51\n- Added message.received as trigger event\n- Added import chatbot\n- Fix lead.fullname.email.update event\n\n# 2.3.50\n- Added new email sending endpoint \n- Emails endpoint is now usable by agents\n- Added route for tiledesk-apps\n- Added route for tiledesk-whatsapp\n- Added route for tiledesk-kaleyra\n- Updated widget.json file\n- Fixed template for: beenInvitedNewUser.html beenInvitedExistingUser.html\n\n# 2.3.49 \n- @tiledesk/tiledesk-tybot-connector\": \"^0.1.22\n\n# 2.3.48\n- added populate_request to root message endpoint for descending \n\n# 2.3.47\n- added root message endpoint for descending \n\n# 2.3.46 \n- @tiledesk/tiledesk-tybot-connector\": \"^0.1.21\n\n# 2.3.45\n- process.env.GLOBAL_SECRET fix\n\n# 2.3.44\n- Removed unused \"sinon\": \"^9.2.4\",\n- Removed unused \"sinon-mongoose\": \"^2.3.0\"\n- Update @tiledesk/tiledesk-tybot-connector: 0.1.19\n\n\n# 2.3.43\n- Package-lock fix\n- @tiledesk/tiledesk-tybot-connector: 0.1.17\n\n\n# 2.3.42\n- Labels update \n\n# 2.3.41\n- Added force parameter to close request \n- Updated dependency tiledesk/tiledesk-tybot-connector\": 0.1.16\n\n# 2.3.40\n- logfix\n\n# 2.3.39\n- logfix\n\n# 2.3.38\n- JobManager require fix\n\n# 2.3.37\n- Created a job worker for geo db, activities and email notification\n- users_util lookup fix\n\n# 2.3.36\n- BugFix email secure with false value. https://tiledesk.discourse.group/t/error-sending-email/180\n\n# 2.3.35\n- Added user util endpoint for contact lookup from chat21\n\n# 2.3.34\n- aqmp depenency fix\n\n# 2.3.32\n- log pub module queue\n\n# 2.3.31\n- logfix\n\n# 2.3.30\n- save multiple messages fix with sequential promises\n- Default JSON_BODY_LIMIT increased to 500KB. Added JSON_BODY_LIMIT env variable\n\n# 2.3.29\n- UIDGenerator class replacement for request route\n- Added hasRole cache for project_user\n- Added index { id_project: 1, role: 1, status: 1, createdAt: 1  } for  Project_user schema\n- Enabled project_user for cacheEnabler class\n- Created cache test for project_user and project\n- Enabled project_user cache for hasRole method\n- Log fix\n- add fields reply and enabled to faq model \n- field answer in faq model is no longer required\n- add field public to faq_kb model\n- when template is undefined the empty template is now created\n- Add unit test for project and project_user\n- Created new endpoint for creating new requests with unit test\n- Created new endpoint for inserting multiple messages at once with unit test\n- New validation for empty text for new message endpoint \n- Added project_user_test route \n- Unit test fix\n\n# 2.3.28\n- UIDGenerator renamed fix\n\n# 2.3.27\n- CacheUtil fix with new values\n- UIDGenerator class replacement\n- Disabled cache for requestService route. Bug:  No matching document found for id \"XYZ\" \n\n# 2.3.26\n- DialogFlow connector fix /tdbot\n\n# 2.3.25\n- Emebedded DialogFlow connector to 1.7.4\n\n# 2.3.24\n- Increased cache TTL from: standardTTL from 120 to 300, longTTL from 1200 to 3600\n- Added cache request.create.simple + cacheEnabler. name fix\n- Disabled unapplicable cache for updateWaitingTimeByRequestId and find request by id REST API\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.14\n- Restored populateMessageWithRequest. Bug with \\agent command. Try to resolve performance with cache\n- Added cache for chat21 webhook event type message. The cache key is without the project id\n- Disabled cache for chat21 webhook conversation archived method\n- Added cache for message event lookup\n- Added cache for chat21 webhook event type message\n- Added cache for getoperator method of department service\n\n\n# 2.3.23\n- cacheEnabler + trigger cache + subscription cache\n- project cache with cacheEnabler\n- request cache with cacheEnabler\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.10\n- Added trigger and subscription cache invalidation rules\n\n# 2.3.22\n- added cacheoose dep package.json\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.8\n\n# 2.3.21\n- log fix\n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.7\n- Moved cache module to public module\n- filter request by smartAssignment\n\n# 2.3.20\n- Performance: Removed populate for message.update messages. This event is not used by anyone. \n\n# 2.3.19\n- Moved queue module to public module\n- Moved route-queue to public module\n- Disable queue module if JOB_WORKER_ENABLED is true\n\n# 2.3.18.7\n- filter request by smartAssignment\n\n# 2.3.18.6\n- logfix\n\n# 2.3.18.1 \n- Updated dependency @tiledesk/tiledesk-tybot-connector to 0.1.10\n\n# 2.3.18\n- Added profileStatus field to the project_user model\n- Added smartAssignment field to the request model                      \n- Canned responses default limit value increased from 40 to 1000\n- Now you change channel name from REST endpoint\n- New Tybot 0.1.5\n- GeoService setImmediate added\n- Websocket setImmediate and topic validation\n- Do not trigger requestNotification send email method for preflight request\n- Removed deprecated .count with .countDocument\n- Added workingStatus to request model                                     \n- Changed Anonymous signin from Guest to guest#shortuid\n- Updated chat21/chat21-node-sdk to 1.1.7. Now group parameter is supported.\n- Moved queue module to public modules folder\n- Created JobManager and Job runner files\n- Now activity archiver module uses queue engine\n- Now geoService uses queue engine\n- Added GEO_SERVICE_ENABLED to enable disable ip lookup to latitude and longitude\n- Added EMAIL_NOTIFICATION_ENABLED to enable disable email notification\n\n\n# 2.3.17 \n- Webhook chat21 fix\n- Chat21 Cloud Function: when an agent leaves a conversation an info message is sent to notify it to the visitor \n\n# 2.3.16\n- Request close activity added as event. Now you can see from the Activity menu who has closed the conversation and when.\n- Log fix for webhook chat21\n\n# 2.3.15\n- BugFix: Endpoint Widget fix for undefined of the project.widget object\n\n# 2.3.14 \n- Added ip filter with Deny roles and ban User roles\n- Ban notifier module\n- Created a new Middleware decodeJwt before passport with passport fallback\n- Removed unused requestService.incrementMessagesCountByRequestId code from chat21Webhook\n- Allow agents to manage groups endpoint\n- Embedded the new Tilebot chat server\n\n# 2.3.13 \n- Getting ip fix\n\n# 2.3.10\n- Added tilebot submodule\n- Askbot endpoint fix for tilebot and auto create faqs for tilebot\n- Added /widgets/ip endpoint   \n- Added bot and subscription as permission check for /intents or /faq\n\n# 2.3.9\n- Rasa process env variable read fix\n\n# 2.3.8\n- @tiledesk/tiledesk-rasa-connector\": \"^1.0.10\n\n# 2.3.7\n- Find by intent_display_name parameter intents\n\n# 2.3.5\n- CHAT_REOPENED updated conversation true\n\n# 2.3.4\n- download pdf fix\n- added no_count and no_textscore fields query parameters to request search endpoint\n\n# 2.3.3\n- Follower notification fix by email\n\n# 2.3.2\n- Dowload trascript as csv, pdf and txt endpoint\n- Added closed_by field to the request model\n- Added followers field to the request model\n- Added lead index\n- Added widget v5 code loader /widgets/v5/:project_id -> heroku blocca cache-control PROVA IN PROD\n- Bugfix  Cannot read property 'profile' of null \n- Added filter by channel offline and online \n- Updated Rasa Connector to 1.0.7   -- QUANDO PORTI IN PROD AGGIORNA SUL DB PER FARLI PUNTARE QUI..\n- Send info message on lead.fullaname.update\n- Follower email notification\n\nhttps://support.zendesk.com/hc/en-us/articles/4408822451482-Using-CCs-followers-and-mentions#topic_wm2_zgq_qgb\n\nFollowers allow you to include additional internal users (agents or administrators) on ticket notifications. Internal users can add followers to tickets. There's no limit to the number of followers you can include on a ticket.\n\nAgents and administrators can use the Followers field in the properties panel of the ticket interface to add internal users to a ticket. \n\nInternal users who can view the ticket can add followers to the ticket.\n\nFollowers can:\n\n* Receive public comments and private comments added to the ticket conversation.\n* Ability to make a private comments and public comment.\n* Replying to a private comment creates a private comment and likewise for public comments.\n* Remove themselves from the ticket conversation.\n* Remain hidden from end users copied on the ticket.\n* Access any ticket that they are following, even if they would not normally be allowed to access the ticket.\n\n# Adding agents as followers from the ticket interface\nIf followers have been enabled by the administrator, internal users (your company's agents and administrators) can add followers from the Followers field from the properties panel in the ticket interface. Followers are internal users such as agents, light agents, and administrators that receive email notifications when a ticket is updated.\n\nNote these things about using followers:\n\n* Followers can add and receive public comments and private comments.\n* Followers can reply to a public comment with a public comment, and reply to a private comment with a private comment.\n* Followers can remove themselves from the ticket.\n* Followers are hidden from copied (CC'd) end users and other followers on email notifications. Their name and email address don't appear in email notifications sent to other users. For more information about the email notifications that followers receive, see Best practices for using email clients with CCs and followers.\n* If your administrator has enabled Automatically make an agent a follower, you can added internal users, such as agents and admins, to tickets as followers from ticket notifications by adding them to the reply as a CC. In this case, the agent becomes both a CC and a follower.\n## To add agents as followers from the ticket interface\n* Select a ticket from one of your views.\nThe Followers field appears in the ticket properties panel on the left side.\n\n* In the Followers field, enter a user's name, email domain, or organization name and the relevant results appear.\nInternal users such as agents, light agents, and administrators can be followers.\n\n* To quickly add yourself as a follower, click follow.\n\n\n\n## To remove a follower from the ticket interface\n* Click the delete button (X) in the person's name box in the Followers list\n\n\n* To quickly remove yourself as a follower, click unfollow.\n\n* To add agents as followers from ticket notifications\n\n* From your email client, open the ticket notification.\n* Open the CC line, per your email provider’s instructions.\n* Add the name of the user you want to add as a follower. Repeat as necessary.\n* Add your comment and send the email.\n\n\n\n# 2.3.1\n- changed tiledesk logo for emails\n- open modules: analytics, activity log, multi tenancy, departments, groups, canned responses, tags, triggers, webhooks\n\n# 2.2.39\n- Added enterprise module\n- Log fix\n- Added DISABLE_MONGO_PASSWORD_MASK env variable\n- Embedded rasa  proxy\n- Added Swedish, Uzbek and Kazakh languages\n- Added Azerbaijani language\n\n# 2.2.38  \n- Unlocked departments, groups, multi-tenant, tags and canned resposes modules\n\n# 2.2.37  -> PROD \n- skip subtype private message for notification\n\n# 2.2.36\n- Ukraine translations\n\n# 2.2.35 \n- BugFix projection for /me service\n\n# 2.2.34  \n- Added transcript webpage for users without system messages\n\n# 2.2.33\n- Request fulltext sort fix\n\n# 2.2.32\n- Added Arabic language for the widget\n- Updated dependencies with npm update\n- Filter requests by lead email\n\n# 2.2.31 (compatible with: Dashboard 2.2.37, Widget 5.0.25)\n- Fix email template reading from project.\n- Fix export messages to csv\n- Fix ip address resolver\n- Exclude poweredBy field from widget endpoint\n- Bugfix when a conversation has a first_text with \\agent\n- Added rasa chatbot chatbot type\n- Added visitor email and fullname in the fulltext index \n\n# 2.2.30\n- Log fix\n\n# 2.2.29 \n- Added endpoint to find requests created by users and guests\n- Log fix\n\n# 2.2.28 (compatible with dasboard ver. 2.2.36)\n- Operator.select returns context object that contains the temp request\n- Added Serbian language to the widget\n- Added tag field to the project_user\n- Removed default BCC from email\n- BugFix: Avoid cluster concurrent jobs in multiple nodes\n- Faq template now support blank and example\n- Organizzation support added\n- ipFilter related to the project is now supported\n- Added filter channel name for the request\n- Added edit card for payment\n- Fix concierge concierge bot for department selection\n- Added filter to find a request by ticket_id\n- Added filter to snap_lead_lead_id for request\n- Added endpoint to close a request by guest\n\n\n# 2.2.26 (compatible with dasboard ver. 2.2.35)\n- Tag fix for 2.2.25\n\n# 2.2.25\n- New label prechat form\n- Updated mongodb-runner from 4.8.1 to 4.8.3 to fix ssh key error\n\n# 2.2.24 \n- webhook subscription can fetch temmates endpoint\n- Added hasBot and createdAt index to the request model\n\n# 2.2.23 \n- Increased list answers limit from 1000 to 3000\n\n# 2.2.22\n- Increased list answers limit from 300 to 1000\n\n# 2.2.21 \n- Increased list answers limit from 100 to 300\n- enabled again waiting time in widgets endpoint unused\n\n\n# 2.2.20\n- disabled waiting time in widgets endpoint unused\n\n# 2.2.18\n- Router logger module enable with ROUTELOGGER_ENABLED=true\n\n# 2.2.17\n- Removed default fallback limit on parse reply\n\n# 2.2.16 \n- Email templates endpoint\n- Created request.updated event for request event and deprecated request.update.comment\n- Added Handlebars template processor for the message transformer module only if message.attributes.templateProcessor=true\n- Email test send endpoint\n- Bugfix widget label\n- Added /intents alias for /faq endpoint\n- The request_id field of the request model has now a unique index\n\n# 2.2.15\n- Added catch messageService.send for bot\n- Added external searcher for bot( ex. Rasa proxy) \n- Faq language fix taken from bot language for create single and import from csv\n- Lower case reset password fix\n- Added alias /bots for /faq_kb\n\n# 2.2.14\n- Fix Tiledesk Queue 1.1.11 with authEvent.queueEnabled = true \n\n# 2.2.13\n- Send message validation with empty text\n\n# 2.2.12\n- Add /bot endpoint\n- Bot and subscription can manage bots\n\n# 2.2.11 \n- Logfix\n\n# 2.2.10\n- Native mqtt auth fix\n\n# 2.2.8\n- Public trigger module\n\n# 2.2.6\n- Quota license fix\n\n# 2.2.4  -> PROD\n- email invitation fix\n\n# 2.2.3\n- Email inboud fix (others disabled and inboudDomain variable fix and token query string encode fix)\n\n# 2.2.2\n- log fix\n\n# 2.2.1\n- log fix\n\n# 2.2.0\n- Cache circleci fix\n- Added EMAIL_REPLY_ENABLED and EMAIL_INBOUND_DOMAIN env parameters.\n- Added API_URL env variable TODO use wehbook url the same as API_URL if not differnet\n\n# 2.1.42 (Compatible with tiledesk-dashboard 2.2.X)\n\n\n- Added ticket_id sequence field to the request model\n- Routing round robin fix (Also in 2.1.40.1)\n- GLOBAL_SECRET env variable fix (Also in 2.1.40.4)\n- Chatbot now support blocked_intent\n- BugFix route request to another department with the same agents (Also in 2.1.40.1)\n- Renamed the chatbot webhook payload field from faq to intent\n- Updated tiledesk-chat-util to 0.8.21  (Also in 2.1.40.1)\n- Removed request first_text replace new line with empty string (for ticketing)\n\n- Fix login problem when email contains upper case char\n- Removed answer field from the fulltext search of the faqs (2.1.40.3)\n\n- Stripe fix for adding new agents (2.1.40.13)\n- Added request delete endpoint by id (Also in 2.1.40.15)\n- Campaigns direct and for group (Also in 2.1.40.16)\n\n- Csv request export added tags (2.1.40.14)\n- Changed request_id to the new standard: support-group-<project_id>-<uid>\n- Added tag to the department model\n- Bugfix first message with an image fix and touchText limited to 30 character or subject (2.1.40.3)\n- Fix request create if department id is not correct\n- MessageRoot endpoint also for group messages (Also in 2.1.40.16)\n- c21 handler group mesages support (Also in 2.1.40.16)\n- Added recipientFullname field to message model. Added save method to messageServive (Also in 2.1.40.16)\n- ChatBot webhook fix when the webhook returns also attributes \n- Messages export csv supported\n- Request util to lookup id_project from request_id (2.1.40.24)\n\n- Find user id from user email endpoint (also in 2.1.40.21)\n- Inizialize enterprise modules before public modules\n- Request Notification fix loading snapshot agents (also in 2.1.40.22)\n- Config secret fix from env (also in 2.1.40.22)\n- Lic ck for users (also in 2.1.40.26)\n- Added s_ticketing_taking_01 trigger\n- Added email template from project settings\n- Faq pagination support\n- For Ticketing send to the cc(s) the agent replies \n- \\agent now is hidden\n- added \\close faq\n- \\close now is hidden\n- set custom role in custom auth using signInWithCustomToken\n- Chat21 contacts find for agent logged with custom auth \n- Added language field to faq_kb and used to specify the language for faq full-text query (default en)\n- Added request priority field\n- Concierge bot fix to reroute only for temp conversation without a bot. Race condition issue when you try manually route a request for example inside a chatbot webhook (Also in 2.1.40.31)\n- Added webhook_enabled parameter to the faqService create method and test refactor\n- Added SYNC_JOIN_LEAVE_GROUP_EVENT environment variable to enable sync between Chat21 (join and leave) and Tiledesk. Default is false. (Also in 2.1.40.32 )\n- Added ALLOW_REOPEN_CHAT environment variable to reopen a chat if a user write after a chat is closed  \n- Used message.received instead message.create in the messageActionsInterceptor to fix race condition sometime occurs with \\close message sent by the bot      \n- Please type your reply above this line Only if replyTo is specified\n- Webhook origin header fix for webhook\n\n\n## Email inbound\n- EmailService supports custom email config with custom SMTP server settings and custom from email\n- Added Tiledesk customer header in the outbound email\n- Added Message-ID and sender (message sender fullname) on the outbound email \n- Added project object to sendRequestTranscript function\n- Welcome label fix key\n- Seamless source page fix\n\n# 2.1.41 \n- remove duplicate request script with: 1619185894304-request-remove-duplicated-request-by-request_id--autosync.js\n- requestNotification improvement not sending email with empty email field\n- Enabled witb DISABLE_SEND_OFFLINE_EMAIL the seamless conversation with email\n- signinWithCustomToken endpoint of the auth router now support id_project body parameter for jwt with generic https://www.tiledesk.com audience field (used by tiledesk-smtp-server)\n- files download endpoint\n- emailService added EMAIL_REPLY_TO parameter;\n- Added email notification for new message and new request for email and form channel (ticket) \n- Added microLanguageTransformationInterceptor enabled when message.attributes.microlanguage==true\n\n# 2.1.40.35\n- Quota license fix\n\n# 2.1.40.34\n- logfix\n\n# 2.1.40.33\n\n- Added setTimeout to resolve race condition for \\close event returned by bot \n\n# 2.1.40.32\n- Added SYNC_JOIN_LEAVE_GROUP_EVENT environment variable to enable sync between Chat21 (join and leave) and Tiledesk. Default is false. \n\n# 2.1.40.31\n- Concierge bot fix to reroute only for temp conversation without a bot. Race condition issue when you try manually route a request for example inside a chatbot webhook \n\n# 2.1.40.30\n- logfix\n\n# 2.1.40.29\n- --production for npm install within Docker for Enterprise\n\n# 2.1.40.28\n- --production for npm install within Docker\n\n# 2.1.40.27\n- Added language field to faq_kb and used to specify the language for faq full-text query \n\n# 2.1.40.26\n- Lic ck bug fix for users\n\n# 2.1.40.25 \n- microLanguageTransformerInterceptor startup error. It is disabled. Module not present\n\n# 2.1.40.24\n- emailService toJSON is not a function fix\n\n# 2.1.40.23\n- requestNotification fix and requestUtilRoot lookup endpoint added\n\n# 2.1.40.22\n- Inizialize enterprise modules before public modules\n- Request Notification fix loading snapshot agents\n\n# 2.1.40.21\n- Find user id from user email endpoint\n\n# 2.1.40.20\n- MessageRoot endpoint validation fix\n\n# 2.1.40.19 \n- Stripe fix with version 1.1.5\n\n# 2.1.40.18\n- Messages export csv supported\n\n# 2.1.40.17\n- Stripe restored to the previous version 1.1.3\n\n# 2.1.40.16\n- added message recipientfullname field to the message entity  to support chat campaigns for direct and group \n\n# 2.1.40.15\n- tag fix\n\n# 2.1.40.14\n- requet CSV export fix\n\n# 2.1.40.13 \n- Stripe fix for adding new agents\n\n# 2.1.40.12\n- Docker image number fix \n\n# 2.1.40.11\n- Docker image number fix \n\n# 2.1.40.10 \n- Docker image number fix \n\n# 2.1.40.9 \n- Added request delete endpoint by id\n\n# 2.1.40.7\n- logfix\n\n# 2.1.40.6\n- Fix request create if department id is not correct\n\n# 2.1.40.5\n- Logfix\n\n# 2.1.40.4\n- GLOBAL_SECRET env variable fix\n\n# 2.1.40.3 \n- Bugfix first message with an image fix and touchText limited to 30 character or subject\n- Removed answer field from the fulltext search of the faqs\n\n\n# 2.1.40.2 \n- log fix\n\n# 2.1.40.1\n- Routing round robin fix\n- Updated tiledesk-chat-util to 0.8.21\n- BugFix route request to another department with the same agents\n\n\n# 2.1.40\n- webhook fix for return empty body\n- log fix\n- Added hasbot filter for GET requests endpoint\n\n# 2.1.39\n- Log fix\n- Chat21 presence  webhook error handler fix \n\n# 2.1.38 -> TILEDESK DASHBOARD v2.1.49.1\n- Added PUT /images/users endpoint where you can archive user's image at the root level of the path\n- Added DELETE /images/users endpoint to delete an image\n- Added PUT /images/users/photo endpoint where you can archive user's avatar at the root level of the path\n- Root messages endpoint fixed\n- Now RestHook module is public\n- Minor deps update\n\n# 2.1.37 \n\n- Trigger module moved to public npm\n- Websocket Message limit with environment variable WS_HISTORY_MESSAGES_LIMIT (300 default)\n- Chat21Webhook now support then new event_type with values \"conversation-archived\" and the old \"deleted-conversation\" (back compatibility). The field recipient_id is renamed to convers_with, but maintained for back compatibility. The project_id is taken from the support-group but also from conversation.attributes for back compatibility.\n- Chat21 group sync now create group with group- prefix. If old group exists (before group- prefix) group updating and deleting can create a duplicate entries\n- Fix intent_info object for is_fallback false\n- Updated dependencies\n- Updated snapshot.lead when a lead is updated. Editing a lead requires to refresh the realtime conversation panel.\n- Lead PUT endpoint fix for status field\n- Websocket error fix when MongoDB is < 4.XX catch snapshot exception\n- Added request.attributes.abandoned_by_project_users when a user leave a chat\n- Send messages direct endpoint\n- Create project_user endpoint by agent (Ticketing) now is with Guest Role\n- Bot webhook error now return the standard message and not the error \n- Added WEBHOOK_ORIGIN env variable for each webhook calls\n\n\n# 2.1.36\n- Minor log fix \n\n# 2.1.35\n- Minor ws log fix \n\n# 2.1.34\n- Minor ws log fix \n\n# 2.1.33\n- Disabled sendUser email for requestNotification\n\n# 2.1.32\n- Project_user endpoint (/get) now can be obtained by uuid_user field\n- Added intent_info to message.attributes sent from bot\n- Pending invite email lowercase fix\n- Project_user invite email lowercase fix\n- Widget endpoint fix for not found project \n- Chat21 WebHook now support also \"message-sent\" event_type value\n\n# 2.1.31\n- Log fix\n\n# 2.1.30 (Requires tiledesk-dashboard 2.1.45+)\n- Added Pending Invitation db indexes\n- The requests queries with status open (!=1000) are not limited for free accounts.   \n- Performance improvment moving request.agents (select false) and availableAgentsCount fields into request.snapshot. Updated @tiledesk-ent/tiledesk-server-triggers:1.1.75. Migration scripts: 1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync 1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync 1616685902635-request_agents_to_snapshot_agents--autosync\n- Added event index to increase analytics performance \n- Snapshot.department fix for ticketing (Without a selected department)  \n- Websocket query is improved (without lead populate and with lean. it's enabled isAuthenticated field of the request.snapshot.requester)\n- Added user object to the event emit method to fix decoded_jwt field of the lead and request\n- The Request deletion also deletes messages    \n\n# 2.1.21\n- BugFix: request availableAgentsCount performance fix\n\n# 2.1.20\n- Default faq fix\n- Log fix\n\n# 2.1.19\n- Default faq with actions example\n\n# 2.1.18\n- Added faq intent_display_name and intent_id. Included database migration script: 1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js\n- Now you can filter the requests by snap_department_routing, req.query.snap_department_default, req.query.snap_department_id_bot, snap_department_id_bot_exists \n- BugFix: Parse FAQ form CSV fix\n- You can enabled Smart Assignment by default for new project with SMART_ASSIGNMENT_CHAT_LIMIT_ON_DEFAULT_PROJECT=true (default = false)\n- Added snapshot object to the request to store embedded snap objects like department, agents, lead, requester.\n- Updated repository dependencies\n- Websocket teammates update fix\n- Now support node 12.x and docker node 12\n- Updated chat21 dependencies (firebase, etc.)\n- Added project_user creation endpoint for Ticketing (POST)\n- Set participants endpoint supports no_populate query param\n- Added request /assing endpoint\n- Now a request can be assinged directly to a partipant without a department for ticket use case.\n- Added Analytics events\n- Added leads filter to retrieve only their with email (with_email=true) and with fullname (with_fullname=true)\n- Now the bot can find actions answers by intent_display_name and intent_id\n- Project_user deletion only for owner\n\n# 2.1.17\n- Log fix\n\n# 2.1.16\n- CSV export fix\n\n# 2.1.15\n\n\n- Automatically close unresponsive bot conversation  (also in 2.1.14.4). You may configure CLOSE_BOT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION, etc.\n- Added _answerid field to the messages replies send from the chatbot under attributes field.\n- Added fulltext indexes for request.notes request.tags and request.subject.. \n- Added default language field for pivoting. We disabled pre traslation for EN, IT, FR. So only pivot language is taken into account. Included database migration script:  1604082287722-labels-data-default-fields--autosync.js\n- Added status field to project_user collection. Included database migration script: 1603797978971-project_users-status-field-added--autosync.js\n- Added channel_type (group or direct) and channel (chat21, whatsapp, etc..) fields to the message model. Included database migration script: 1602847963299-message-channel_type-and-channel-fields-added--autosync.js\n- Added posfix $reply_time to WAITING_TIME_FOUND label. Included database migration script: 1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js\n- Added channelOutbound (chat21, whatsapp, etc..) fields to indentify the outboud channel to the request model. Included database migration script: 1603955232377-requests-channel-outbound-fields--autosync.js\n- Renamed request field UNSERVED (100) to UNASSIGNED (100) and SERVED (200) to ASSIGNED (200)\n- Renamed property max_agent_served_chat to max_agent_assigned_chat of the project.settings object.Included database migration script: project-settings-max_agent_assigned_renamed--autosync\n- Renamed property max_served_chat to max_assigned_chat of the project_user object. Included database migration script: project_user-max_assigned_chat-renamed--autosync\n- SourcePage and bot answer stats with tiledesk-ent/tiledesk-server-analytics: 1.1.9\n- Fix Conversation export to CSV\n- Bots statistics with tiledesk-ent/tiledesk-server-analytics: 1.1.8\n- Added support to channel selection for resthook module @tiledesk-ent/tiledesk-server-resthook: 1.1.47\n- Added supervisor role\n- BugFix: Updated jwthistory and queue module with listen function fix (also in 2.1.14.1)\n- Department patch method added (also in 2.1.14.2)\n- BugFix: Now you can signupWithCustomToken using subject=userexternal and subscription type audience (https://tiledesk.com/subscriptions/subid). It's used by Whatsapp and Facebook Messenger apps (also in 2.1.14.2)\n- Chat21 contact detail endpoint\n- Added tags field for the lead model\n- Added location field to the request model. Auto populate location field from ip\n- Update all dependencies to last version\n- Image endpoint now return also thumbnail filename\n- Added analytics endpoint for messages (also in 2.1.14.2)\n- New websocket return also events model (beta)\n- Log fix for signup and signin endpoint (also in 2.1.14.2)\n- Added email notification setting for each teammate (also in 2.1.14.3)\n- Added email notification setting for each project\n- Bugfix (@tiledesk-ent/tiledesk-server-triggers\":1.1.69) for chatbot invitation race condition with Chat21 createGroup and setMembers method. Now the trigger listens to requestEvent.on(\"request.support_group.created\",..) event and not to requestEvent.on(\"request.create\",..) event. This doen't require data migration for old triggers (also in 2.1.14.4).\n- Added plugin to save log to MongoDB (also in 2.1.14.4) with WRITE_LOG_TO_MONGODB=true, LOG_MONGODB_LEVEL, DATABASE_LOG_URI\n- Added plugin to save multi-tenant log to MongoDB with WRITE_LOG_MT_TO_MONGODB=true, LOG_MT_MONGODB_LEVEL\n- Added DEFAULT_FULLTEXT_INDEX_LANGUAGE env parameter for Faq, Lead, message and requests. Before the index language was Italian.\n- Added support to \\close action\n- Disabled send trascript email for autoclosed conversations #413\n\n# 2.1.14.5 -> Cloud Production\n- Winston MongoDB Log fix\n\n# 2.1.14.4\n- Automatically close unresponsive bot conversation. You may configure CLOSE_BOT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION, etc.\n- Bugfix (@tiledesk-ent/tiledesk-server-triggers\":1.1.69) for chatbot invitation race condition with Chat21 createGroup and setMembers method. Now the trigger listens to requestEvent.on(\"request.support_group.created\",..) event and not to requestEvent.on(\"request.create\",..) event. This doen't require data migration for old triggers.\n- Added plugin to save log to MongoDB \n\n# 2.1.14.3\nAdded email notification setting for each teammate (also in 2.1.14.3)\n\n# 2.1.14.2\n- Department patch method added (ok)\n- BugFix: Now you can signupWithCustomToken using subject=userexternal and subscription type audience (https://tiledesk.com/subscriptions/subid). It's used by Whatsapp and Facebook Messenger apps . UNISALENTO TEST\n- Added analytics endpoint for messages \n- Log fix for signup and signin endpoint.\n\n# 2.1.14.1\n- Updated queue module with listen function fix\n\n# 2.1.14\n- Renamed field presence.lastOnlineAt to changedAt\n- Added filter presencestatus for project_users endpoint\n- Websocket events updated with filters\n\n# 2.1.13\n- Added Cors options pre-flight with \n- Fix TD218 Audit log for user invitation already registered\n\n# 2.1.12\n- Chat21 engine selection with CHAT21_ENGINE=[mqtt|firebase]\n- Schema migration tool with mongoose-migrate. Added env property DISABLE_AUTO_SHEMA_MIGRATION\n- #TD-250 Added emoji callout\n- Cors fix. Removed alternative cors response header.\n- Channel manager refactoring\n- Event route fix and ws event endpoint\n- WS projectì_user endpoint is now usable by other teammate\n- Message text required only for type text messages\n- #TD-251 Email lower case fix\n- Updated Node default engine to 11.15.0\n- Added hasBot query filter to retrive conversations with or without a bot\n- Chatbot \\frame action command supported\n- Supported components: tiledesk-dashboard:2.0.73+ chat21-webwidget:4.0.75+ chat21-ionic:2.0.12\n\n# 2.1.11\n- Mongo support for Winston with: WRITE_LOG_TO_MONGODB=true \n- Logfix\n\n# 2.1.10\n- Tiledesk Chat21 groups syncronizer. Enable with SYNC_CHAT21_GROUPS=\"true\"\n- Built-in faq updated  and chatbot webhook example changed\n- Return role: admin if the admin sign-in with email and password\n- Default fallback event emitting\n- Files and images storage services\n- Supported components: tiledesk-dashboard:2.0.70 chat21-webwidget:4.0.73 chat21-ionic:2.0.12\n\n# 2.1.9\n- Widget departments fix\n\n# 2.1.8\n- SigninWithCustomToken fix for different audience types\n\n# 2.1.7\n- Minor fix\n\n# 2.1.6 \n- Push notification fix for the first support message and for group joining.\n\n# 2.1.5\n- Email template externalization with handlebars under /template/email folder. \n You can override the email template using EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE, EMAIL_POOLED_REQUEST_HTML_TEMPLATE,\n EMAIL_RESET_PASSWORD_HTML_TEMPLATE, EMAIL_PASSWORD_CHANGED_HTML_TEMPLATE, EMAIL_EXUSER_INVITED_HTML_TEMPLATE,\n EMAIL_NEWUSER_INVITED_HTML_TEMPLATE, EMAIL_VERIFY_HTML_TEMPLATE, EMAIL_SEND_TRANSCRIPT_HTML_TEMPLATE env variables\n\n# 2.1.4\n- Email config parameters fix\n\n# 2.1.3\n- Language pivot fix\n\n# 2.1.2\n- License fix\n\n# 2.1.1\n- Minor bug fix\n\n# 2.1.0\n- Release\n\n# 2.1.0-beta24\n- Minor bug fix\n\n# 2.1.0-beta23\n- Email fix for pooled request caming from bot\n\n# 2.1.0-beta22 \n- filter identity bot. use all=true to get all bot. Require Dashboard 2.0.49\n- Chat21 Contacts for web chat recipients list\n- Chat21 Presence webhook \n- Widget i8n\n\n\n# 2.1.0-beta14 # 2.1.0-beta15 # 2.1.0-beta16 # 2.1.0-beta..20\n- Docker tag\n\n# 2.1.0-beta13\n- add canned responses and tag library\n- add groups and departments modules\n- cache, routing, resthook deps update\n- added request.status all for rest api\n- request and lead physical deletion\n- add mt and visitor counter modules\n- removed reqLog feature\n- typing webhook fix\n- label fix with no pivoting to default languages\n- removed terminus\n\n# 2.1.0-beta12\n- Send transcript fix\n\n# 2.1.0-beta11\n- Activity archiver fix for preflight request\n- request physical deletion\n\n# 2.1.0-beta10\n- Minor bugfix for events\n\n# 2.1.0-beta9\n- Trigger fix for custom authentication\n\n# 2.1.0-beta7\n- Activities, Jwt History and Rest Hook update\n\n# 2.1.0-beta6\n- Widget language pivoting fix\n\n# 2.1.0-beta5\n- Support for cache\n- Removed message_count of request model\n- Concierge bot now update lead after preflight\n\n# 2.1.0-beta4\n- added isAuthenticated field to project_user model\n- trigger update \n- set requester to request create of the chat21 webhook\n\n# 2.1.0-beta2\n- added field participantsAgents, participantsBot and hasBot fields. Migration file updated\n- request, department, project, project_users, subscription indexes added\n- conciergeBot multilanguage improvement\n- trigger module updated\n- resthook module updated with minor fixes\n- added fanout pub sub to queue module and added support for *.queue.pubsub events\n- WS_HISTORY_REQUESTS_LIMIT env variable added\n\n# 2.1.0-beta1 \n- project rest api order fix by updatedAt\n- improved internal bot with defaultFallback and smart text and webhook\n- messageService.send with metadata field\n- widget and test page route\n- Dockerfile fix (removed nano and nodemon)\n- invite teammate rest api with available or unavailable \n- welcome message when a request is assigned to an agent (TOUCHING_OPERATOR)\n- added department description and bot description fields\n- added request rating rest api (PATCH)\n- implemented \\start command \n- trigger improvement \n- added request status (TEMP=50) and preflight field (exclude request.preflight=true from ws) \n- concierge bot now supports switch from preflight to standard request (rerouting, preflight update, first_text update) \n- added typing event \n- created Message Transformation Engine for multilanguage message (labels with the new labelService) and text templating\n- added language field to message model and indexes improvement\n- tag and tagLibrary refactoring \n- resigninAnonymously rest api for widget re-authenticate\n- email notification improvement for agent joing \n- added label for office closed\n- added queue for Enterprise version (websocket)\n- Websocket pub/sub fix with handlePublishMessageToClientId\n- Websocket performance fix with lean and removing populate \n- Added kubernetes sample config file\n- Added required firstname and lastname to signup endpoint\n- Removed message.request.messages and message.request.department.bot for message.create event  "
  },
  {
    "path": "Dockerfile",
    "content": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\n\nRUN apt-get update\n\n# Create app directory\nWORKDIR /usr/src/app\n\nARG NPM_TOKEN\n\nRUN if [ \"$NPM_TOKEN\" ]; \\\n    then RUN COPY .npmrc_ .npmrc \\\n    else export SOMEVAR=world; \\\n    fi\n\n\n# Install app dependencies\n# A wildcard is used to ensure both package.json AND package-lock.json are copied\n# where available (npm@5+)\nCOPY package*.json ./\n\nRUN npm install --production\n\nRUN rm -f .npmrc\n\n# Bundle app source\nCOPY . .\n\nEXPOSE 3000\n\nCMD [ \"npm\", \"start\" ]\n\n"
  },
  {
    "path": "Dockerfile-en",
    "content": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\nRUN apt-get update\n\n# Create app directory\nWORKDIR /usr/src/app\n\n# Accept build arguments\nARG NPM_TOKEN\nARG VOICE_TOKEN\nARG VOICE_TWILIO_TOKEN\n\nCOPY .npmrc_ .npmrc\n\n# Set environment variable based on build argument\nENV VOICE_TOKEN=${VOICE_TOKEN}\nENV VOICE_TWILIO_TOKEN=${VOICE_TWILIO_TOKEN}\n\n# Install app dependencies\n# A wildcard is used to ensure both package.json AND package-lock.json are copied\n# where available (npm@5+)\nCOPY package*.json ./\n\nRUN npm install --production\n\nRUN rm -f .npmrc\n\n# Bundle app source\nCOPY . .\n\nEXPOSE 3000\n\nCMD [ \"npm\", \"start\" ]\n\n"
  },
  {
    "path": "Dockerfile-jobs",
    "content": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\nRUN apt-get update\n\n# Create app directory\nWORKDIR /usr/src/app\n\nARG NPM_TOKEN\n\nRUN if [ \"$NPM_TOKEN\" ]; \\\n    then RUN COPY .npmrc_ .npmrc \\\n    else export SOMEVAR=world; \\\n    fi\n\n\n# Install app dependencies\n# A wildcard is used to ensure both package.json AND package-lock.json are copied\n# where available (npm@5+)\nCOPY package*.json ./\n\nRUN npm install --production\n\nRUN rm -f .npmrc\n\n# Bundle app source\nCOPY . .\n\nEXPOSE 3000\n\nCMD [ \"node\", \"jobs.js\" ]\n\n"
  },
  {
    "path": "Dockerfile-profiler",
    "content": "FROM node:18-bullseye\n\nRUN sed -i 's/stable\\/updates/stable-security\\/updates/' /etc/apt/sources.list\n\n\nRUN apt-get update\n\n# Create app directory\nWORKDIR /usr/src/app\n\nARG NPM_TOKEN\n\nRUN if [ \"$NPM_TOKEN\" ]; \\\n    then RUN COPY .npmrc_ .npmrc \\\n    else export SOMEVAR=world; \\\n    fi\n\n\n# Install app dependencies\n# A wildcard is used to ensure both package.json AND package-lock.json are copied\n# where available (npm@5+)\nCOPY package*.json ./\n\nRUN npm install --production\n\nRUN rm -f .npmrc\n\n# Bundle app source\nCOPY . .\n\nEXPOSE 3000\n\nCMD [ \"node\", \"--prof\", \"./bin/www\" ]\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Tiledesk\n\nPermission is hereby granted, free of charge, \nto any person obtaining a copy of this software and associated documentation files (the \"Software\"), \nto deal in the Software without restriction, including without limitation the rights to use, \ncopy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, \nand to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, \nEXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, \nFITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. \nIN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, \nWHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[![npm version](https://badge.fury.io/js/%40tiledesk%2Ftiledesk-server.svg)](https://badge.fury.io/js/%40tiledesk%2Ftiledesk-server)\n\n[![CircleCI](https://circleci.com/gh/Tiledesk/tiledesk-server.svg?style=svg)](https://circleci.com/gh/Tiledesk/tiledesk-server)\n\n\n> ***🚀 Do you want to install Tiledesk on your server with just one click?***\n> \n> ***Use [Docker Compose Tiledesk installation](https://github.com/Tiledesk/tiledesk-deployment/blob/master/docker-compose/README.md) guide***\n\n# Introduction\n\nTiledesk-server is the server engine of Tiledesk. Tiledesk is an Open Source Live Chat platform with integrated Chatbots written in NodeJs and Express. Build your own customer support with a multi-channel platform for Web, Android and iOS. \n\nDesigned to be open source since the beginning, we actively worked on it to create a totally new, first class customer service platform based on instant messaging.\n\nWhat is Tiledesk today? It became the open source “conversational app development” platform that everyone needs 😌\n\nYou can use Tiledesk to increase sales for your website or for post-sales customer service. Every conversation can be automated using our first class native chatbot technology.\nYou can also connect your own applications using our APIs or Webhooks.\nMoreover you can deploy entire visual applications inside a conversation. And your applications can converse with your chatbots or your end-users! We know this is cool 😎\n\nTiledesk is multichannel in a totally new way. You can write your chatbot scripts with images, buttons and other cool elements that your channels support. But you will configureyour chatbot replies only once. They will run on every channel, auto-adapting the responses to the target channel whatever it is, Whatsapp, Facebook Messenger, Telegram etc.\n\nMore info on Tiledesk website: https://www.tiledesk.com.\n\nYou can find technical documentation here: https://developer.tiledesk.com\n\n# Prerequisites for Installation\n\n* [Nodejs](https://www.npmjs.com/) and npm installed. Suggested versions are NodeJS 12.20.2 and NPM 6.14.11 \n* [MongoDb](https://www.mongodb.com) installed\n\n# Run Tiledesk with Docker Compose\n\nDo you want to install all the Tiledesk components on your server with just one click?\nUse [Docker Compose Tiledesk installation guide](https://github.com/Tiledesk/tiledesk-deployment/blob/master/docker-compose/README.md)\n\n# Running Tiledesk Server\n\n## Using Docker\n\n\n### Configure .env file \n```\ncurl https://raw.githubusercontent.com/Tiledesk/tiledesk-server/master/.env.sample --output .env\nnano .env #configure .env file properly\n```\n\n### Running\nIf you want to run tiledesk and mongo with docker run :\n\n```\ndocker run --name tiledesk-mongo -d mongo\ndocker run -p 3000:3000 --env DATABASE_URI=\"mongodb://mongo/tiledesk-server\" --env-file .env --link tiledesk-mongo:mongo tiledesk/tiledesk-server\n```\n\nOtherwise if you want to run tiledesk only with docker run :\n\n```\ndocker run -p 3000:3000 --env DATABASE_URI=\"mongodb://YOUR_MONGO_INSTALLATION_ENDPOINT/tiledesk-server\" --env-file .env tiledesk/tiledesk-server\n```\n\n\n\n## Run locally with npm\n\nSteps to run with npm:\n\n```\nnpm install -g @tiledesk/tiledesk-server mongodb-runner\nmongodb-runner start\ncurl https://raw.githubusercontent.com/Tiledesk/tiledesk-server/master/.env.sample --output .env\nnano .env #configure .env file properly\ntiledesk-server  \n```\n\nIf you want to load .env file from another path: `DOTENV_PATH=/MY/ABSOLUTE/PATH/.env tiledesk-server`\n\nNote: If installation with -g fails due to permission problems (npm ERR! code 'EACCES'), please refer to this [link](https://docs.npmjs.com/getting-started/fixing-npm-permissions).\n\n## Install from source code\n\n* Clone this repo\n* Install dependencies with `npm install`\n* Configure the tiledesk .env file. You can see an example in the file .env.sample under the root folder. Rename it from .env.sample to .env and configure it properly. \n* Run the app with the command `npm start`.\n\n\n## Deploy on Heroku\n\nDeploy with button:\n\n[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/Tiledesk/tiledesk-server)\n\n# Community? Questions? Support ?\nIf you need help or just want to hang out, come, say hi on our [<img width=\"15\" alt=\"Tiledesk discord\" src=\"https://seeklogo.com/images/D/discord-color-logo-E5E6DFEF80-seeklogo.com.png\"> Discord](https://discord.gg/Q5A6Ewadmz) server.\n\n# REST API\n\nSee the Tiledesk REST API [here](https://developer.tiledesk.com/apis/rest-api/introduction)\n\n# Upgrading \nTo see how to upgrade tiledesk-server see [here](./docs/upgrading.md) \n\n# Testing\nRun unit test with `npm test` and integration test with `npm run test:int` \n\n\n"
  },
  {
    "path": "app.js",
    "content": "var dotenvPath = undefined;\n\nif (process.env.DOTENV_PATH) {\n  dotenvPath = process.env.DOTENV_PATH;\n  console.log(\"load dotenv form DOTENV_PATH\", dotenvPath);\n}\n\nif (process.env.LOAD_DOTENV_SUBFOLDER ) {\n  console.log(\"load dotenv form LOAD_DOTENV_SUBFOLDER\");\n  dotenvPath = __dirname+'/confenv/.env';\n}\n\nrequire('dotenv').config({ path: dotenvPath});\n\n\nvar express = require('express');\nvar path = require('path');\n// var logger = require('morgan');\nvar cookieParser = require('cookie-parser');\nvar bodyParser = require('body-parser');\nvar morgan = require('morgan');\nvar mongoose = require('mongoose');\n\nvar passport = require('passport');\nrequire('./middleware/passport')(passport);\n\nvar config = require('./config/database');\nvar cors = require('cors');\nvar Project = require(\"./models/project\");\nvar validtoken = require('./middleware/valid-token');\nvar roleChecker = require('./middleware/has-role');\n\nconst MaskData = require(\"maskdata\");\nvar winston = require('./config/winston');\n\n\n// DATABASE CONNECTION\n\n// https://bretkikehara.wordpress.com/2013/05/02/nodejs-creating-your-first-global-module/\nvar databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\n\nif (!databaseUri) { //TODO??\n  winston.warn('DATABASE_URI not specified, falling back to localhost.');\n}\n\nif (process.env.NODE_ENV == 'test')  {\n  databaseUri = config.databasetest;\n}\n\nconst masked_databaseUri = MaskData.maskPhone(databaseUri, {\n        maskWith : \"*\",\n        unmaskedStartDigits: 15, \n        unmaskedEndDigits: 5\n      });\n\nif (process.env.DISABLE_MONGO_PASSWORD_MASK ==true || process.env.DISABLE_MONGO_PASSWORD_MASK == \"true\")  {\n  winston.info(\"DatabaseUri: \" + databaseUri);\n}else {\n  winston.info(\"DatabaseUri masked: \" + masked_databaseUri);\n}\n\n\nvar autoIndex = true;\nif (process.env.MONGOOSE_AUTOINDEX) {\n  autoIndex = process.env.MONGOOSE_AUTOINDEX;\n}\nwinston.info(\"DB AutoIndex: \" + autoIndex);\n\nlet useUnifiedTopology = process.env.MONGOOSE_UNIFIED_TOPOLOGY === 'true';\nwinston.info(\"DB useUnifiedTopology: \", useUnifiedTopology, typeof useUnifiedTopology);\n\nvar connection = mongoose.connect(databaseUri, { \"useNewUrlParser\": true, \"autoIndex\": autoIndex, \"useUnifiedTopology\": useUnifiedTopology }, function(err) {\n  if (err) { \n    winston.error('Failed to connect to MongoDB on ' + databaseUri + \" \", err);\n    process.exit(1);\n  }\n  winston.info(\"Mongoose connection done on host: \"+mongoose.connection.host + \" on port: \" + mongoose.connection.port + \" with name: \"+ mongoose.connection.name)// , mongoose.connection.db);\n});\nif (process.env.MONGOOSE_DEBUG===\"true\") {\n  mongoose.set('debug', true);\n}\nmongoose.set('useFindAndModify', false); // https://mongoosejs.com/docs/deprecations.html#-findandmodify-\nmongoose.set('useCreateIndex', true);\n//mongoose.set('useUnifiedTopology', false); \n\n// CONNECT REDIS - CHECK IT\nconst { TdCache } = require('./utils/TdCache');\nlet tdCache = new TdCache({\n    host: process.env.CACHE_REDIS_HOST,\n    port: process.env.CACHE_REDIS_PORT,\n    password: process.env.CACHE_REDIS_PASSWORD\n});\n\ntdCache.connect();\n\n// ROUTES DECLARATION\nvar troubleshooting = require('./routes/troubleshooting');\nvar auth = require('./routes/auth');\nvar authtest = require('./routes/authtest');\nvar authtestWithRoleCheck = require('./routes/authtestWithRoleCheck');\n\nvar lead = require('./routes/lead');\nvar message = require('./routes/message');\nvar messagesRootRoute = require('./routes/messagesRoot');\nvar department = require('./routes/department');\nvar group = require('./routes/group');\nvar resthook = require('./routes/subscription');\nvar tag = require('./routes/tag');\nvar faq = require('./routes/faq');\nvar faq_kb = require('./routes/faq_kb');\nvar project = require('./routes/project');\nvar project_user = require('./routes/project_user');\nvar project_users_test = require('./routes/project_user_test');\nvar request = require('./routes/request');\n// var setting = require('./routes/setting');\nvar users = require('./routes/users');\nvar usersUtil = require('./routes/users-util');\nvar publicRequest = require('./routes/public-request');\nvar userRequest = require('./routes/user-request');\nvar publicAnalytics = require('./routes/public-analytics');\nvar pendinginvitation = require('./routes/pending-invitation');\nvar jwtroute = require('./routes/jwt');\nvar key = require('./routes/key');\nvar widgets = require('./routes/widget');\nvar widgetsLoader = require('./routes/widgetLoader');\nvar openai = require('./routes/openai');\nvar llm = require('./routes/llm');\nvar quotes = require('./routes/quotes');\nvar integration = require('./routes/integration')\nvar kbsettings = require('./routes/kbsettings');\nvar kb = require('./routes/kb');\nvar unanswered = require('./routes/unanswered');\nvar answered = require('./routes/answered');\n\n// var admin = require('./routes/admin');\nvar faqpub = require('./routes/faqpub');\nvar labels = require('./routes/labels');\nvar fetchLabels = require('./middleware/fetchLabels');\nvar cacheUtil = require(\"./utils/cacheUtil\");\nvar orgUtil = require(\"./utils/orgUtil\");\nvar images = require('./routes/images');\nvar files = require('./routes/files');\nlet filesp = require('./routes/filesp');\nvar campaigns = require('./routes/campaigns');\nvar logs = require('./routes/logs');\nvar requestUtilRoot = require('./routes/requestUtilRoot');\nvar urls = require('./routes/urls');\nvar email = require('./routes/email');\nvar property = require('./routes/property');\nvar segment = require('./routes/segment');\nvar webhook = require('./routes/webhook');\nvar webhooks = require('./routes/webhooks');\nvar roles = require('./routes/roles');\nvar copilot = require('./routes/copilot');\nvar mcp = require('./routes/mcp');\n\nvar bootDataLoader = require('./services/bootDataLoader');\nvar settingDataLoader = require('./services/settingDataLoader');\nvar schemaMigrationService = require('./services/schemaMigrationService');\nvar RouterLogger = require('./models/routerLogger');\nvar cacheEnabler = require(\"./services/cacheEnabler\");\nconst session = require('express-session');\nconst RedisStore = require(\"connect-redis\").default\nconst botEvent = require('./event/botEvent');\n\nrequire('./services/mongoose-cache-fn')(mongoose);\n\n\nvar subscriptionNotifier = require('./services/subscriptionNotifier');\nsubscriptionNotifier.start();\n\nvar subscriptionNotifierQueued = require('./services/subscriptionNotifierQueued');\n\n\nvar botSubscriptionNotifier = require('./services/BotSubscriptionNotifier');\nbotSubscriptionNotifier.start(); //queued but disabled\n\nbotEvent.listen(); //queued but disabled\n\nvar trainingService = require('./services/trainingService');\ntrainingService.start();\n\n// job_here\n\nvar geoService = require('./services/geoService');\n// geoService.listen(); //queued\n\nvar updateLeadQueued = require('./services/updateLeadQueued');\nvar updateRequestSnapshotQueued = require('./services/updateRequestSnapshotQueued');\n\nlet JobsManager = require('./jobsManager');\n\nlet jobWorkerEnabled = false;\nif (process.env.JOB_WORKER_ENABLED==\"true\" || process.env.JOB_WORKER_ENABLED == true) {\n    jobWorkerEnabled = true;\n}\nwinston.info(\"JobsManager jobWorkerEnabled: \"+ jobWorkerEnabled);  \n\nlet jobsManager = new JobsManager(jobWorkerEnabled, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued, updateRequestSnapshotQueued);\n\nvar faqBotHandler = require('./services/faqBotHandler');\nfaqBotHandler.listen();\n\nvar pubModulesManager = require('./pubmodules/pubModulesManager');\npubModulesManager.init({express:express, mongoose:mongoose, passport:passport, databaseUri:databaseUri, routes:{}, jobsManager:jobsManager, tdCache:tdCache});\n  \njobsManager.listen(); //listen after pubmodules to enabled queued *.queueEnabled events\n\nlet whatsappQueue = require('@tiledesk/tiledesk-whatsapp-jobworker');\nwinston.info(\"whatsappQueue\");\njobsManager.listenWhatsappQueue(whatsappQueue);\n\nlet multiWorkerQueue = require('@tiledesk/tiledesk-multi-worker');\nwinston.info(\"multiWorkerQueue from App\")\njobsManager.listenMultiWorker(multiWorkerQueue);\n\nvar channelManager = require('./channels/channelManager');\nchannelManager.listen(); \n\nvar IPFilter = require('./middleware/ipFilter');\n\n// job_here\nvar BanUserNotifier = require('./services/banUserNotifier');\nBanUserNotifier.listen();\nconst { ChatbotService } = require('./services/chatbotService');\nconst { QuoteManager } = require('./services/QuoteManager');\nconst RateManager = require('./services/RateManager');\n\nlet qm = new QuoteManager({ tdCache: tdCache });\nqm.start();\n\nlet rm = new RateManager({ tdCache: tdCache });\n\n\nvar modulesManager = undefined;\ntry {\n  modulesManager = require('./services/modulesManager');\n  modulesManager.init({express:express, mongoose:mongoose, passport:passport, routes: {departmentsRoute: department, projectsRoute: project, widgetsRoute: widgets} });\n} catch(err) {\n  winston.info(\"ModulesManager not present\");\n}\n\n\n//enterprise modules can modify pubmodule\nmodulesManager.start();\n\npubModulesManager.start();\n\n\nsettingDataLoader.save();\nschemaMigrationService.checkSchemaMigration();\n\nif (process.env.CREATE_INITIAL_DATA !== \"false\") {\n   bootDataLoader.create();\n}\n\n\n\n\nvar app = express();\n\n// view engine setup\napp.set('views', path.join(__dirname, 'views'));\napp.set('view engine', 'jade');\n\napp.set('chatbot_service', new ChatbotService())\napp.set('redis_client', tdCache);\napp.set('quote_manager', qm);\napp.set('rate_manager', rm);\n\n// TODO DELETE IT IN THE NEXT RELEASE\nif (process.env.ENABLE_ALTERNATIVE_CORS_MIDDLEWARE === \"true\") {  \n  app.use(function (req, res, next) {\n    res.header(\"Access-Control-Allow-Origin\", \"*\"); //qui dice cequens attento\n    // var request_cors_header = req.headers[\"\"]\n    res.header(\"Access-Control-Allow-Headers\", \"Origin, X-Requested-With, Content-Type, Accept, Authorization, x-xsrf-token\");\n    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS')\n    next();\n  });\n\n  winston.info(\"Enabled alternative cors middleware\");\n} else {\n  winston.info(\"Used standard cors middleware\");\n}\n\n\n// uncomment after placing your favicon in /public\n//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));\n// app.use(morgan('dev'));\n// app.use(morgan('combined'));\n\n\n// app.use(bodyParser.json());\n\n// https://stackoverflow.com/questions/18710225/node-js-get-raw-request-body-using-express\n\n\nconst JSON_BODY_LIMIT = process.env.JSON_BODY_LIMIT || '500KB';\nwinston.debug(\"JSON_BODY_LIMIT : \" + JSON_BODY_LIMIT);\n\nconst WEBHOOK_BODY_LIMIT = process.env.WEBHOOK_BODY_LIMIT || '5mb';\nwinston.debug(\"WEBHOOK_BODY_LIMIT : \" + WEBHOOK_BODY_LIMIT);\n\nconst webhookParser = bodyParser.json({ limit: WEBHOOK_BODY_LIMIT });\n\napp.use('/webhook', webhookParser, webhook);\n\napp.use(bodyParser.json({limit: JSON_BODY_LIMIT,\n  verify: function (req, res, buf) {\n    // var url = req.originalUrl;\n    // if (url.indexOf('/stripe/')) {\n      req.rawBody = buf.toString();\n      winston.debug(\"bodyParser verify stripe\", req.rawBody);\n    // } \n  }\n}));\n\napp.use(bodyParser.urlencoded({limit: JSON_BODY_LIMIT, extended: true }));\n\napp.use(cookieParser());\napp.use(express.static(path.join(__dirname, 'public')));\n// app.use('/uploads', express.static(path.join(__dirname, 'uploads')));\n\n//app.use(morgan('dev'));\n\nif (process.env.ENABLE_ACCESSLOG) {\n  app.use(morgan('combined', { stream: winston.stream }));\n}\n\napp.use(passport.initialize());\n// If deployed behind a proxy/ingress (TLS terminated upstream), enable this\n// if (process.env.TRUST_PROXY === \"true\") {\napp.set('trust proxy', 1);\n// }\n\n// After you declare \"app\"\nif (process.env.DISABLE_SESSION_STRATEGY==true ||  process.env.DISABLE_SESSION_STRATEGY==\"true\" ) {\n  winston.info(\"Express Session disabled\");\n} else {\n\n  // https://www.npmjs.com/package/express-session\n  let sessionSecret = process.env.SESSION_SECRET || \"tiledesk-session-secret\";\n\n  if (process.env.ENABLE_REDIS_SESSION==true ||  process.env.ENABLE_REDIS_SESSION==\"true\" ) {\n  \n      console.log(\"Starting redis...\") // errors occurs\n      // Initialize client.\n      // let redisClient = createClient()\n      // redisClient.connect().catch(console.error)\n\n      let cacheClient = undefined;\n      if (pubModulesManager.cache) {\n        cacheClient = pubModulesManager.cache._cache._cache;  //_cache._cache to jump directly to redis modules without cacheoose wrapper (don't support await)\n      }\n      // winston.info(\"Express Session cacheClient\",cacheClient);\n\n\n      let redisStore = new RedisStore({\n        client: cacheClient,\n        prefix: \"sessions:\",\n      })\n\n      app.use(\n        session({\n          store: redisStore,\n          resave: false, // required: force lightweight session keep alive (touch)\n          saveUninitialized: false, // recommended: only save session when data exists\n          secret: sessionSecret,\n          cookie: {\n            secure: true,           // ✅ Use HTTPS\n            httpOnly: true,         // ✅ Only accessible by the server (not client-side JS)\n            sameSite: 'None'        // ✅ Allows cross-origin (e.g., Keycloak on a different domain)\n          }\n        })\n      )\n      winston.info(\"Express Session with Redis enabled with Secret: \" + sessionSecret);\n\n\n  } else {\n    app.use(session({ secret: sessionSecret}));\n    winston.info(\"Express Session enabled with Secret: \" + sessionSecret);\n\n  }\n\n  app.use(passport.session());\n  \n  \n}\n\n//ATTENTION. If you use AWS Api Gateway you need also to configure the cors policy https://docs.aws.amazon.com/apigateway/latest/developerguide/how-to-cors-console.html\napp.use(cors());\napp.options('*', cors());\n\n// const customRedisRateLimiter = require(\"./rateLimiter\").customRedisRateLimiter;\n// app.use(customRedisRateLimiter);\n\n// MIDDLEWARE FOR REQUESTS QUOTE\n// app.use('/:projectid/requests', function (req, res, next) {\n  \n//   console.log(\"MIDDLEWARE FIRED ---> REQUESTS\");\n//   console.log(\"(Requests Middleware) method: \", req.method);\n//   if (req.method === 'POST') {\n\n//   let quoteManager = new QuoteManager({ project: mockProject, tdCache: mockTdCache } )\n    \n//   } else {\n//     next();\n//   }\n\n\n// });\nconsole.log(\"MAX_UPLOAD_FILE_SIZE: \", process.env.MAX_UPLOAD_FILE_SIZE);\n\n\nif (process.env.ROUTELOGGER_ENABLED===\"true\") {\n  winston.info(\"RouterLogger enabled \");\n  app.use(function (req, res, next) {\n    // winston.error(\"log \", req);\n\n      try {\n        var projectid = req.projectid;\n        winston.debug(\"RouterLogger projectIdSetter projectid:\" + projectid);\n\n      var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n      winston.debug(\"fullUrl:\"+ fullUrl);\n      winston.debug(\" req.get('host'):\"+  req.get('host'));\n     \n      winston.debug(\"req.get('origin'):\" + req.get('origin'));\n      winston.debug(\"req.get('referer'):\" + req.get('referer'));\n\n      var routerLogger = new RouterLogger({\n        origin: req.get('origin'),\n        fullurl: fullUrl,    \n        url: req.originalUrl.split(\"?\").shift(),    \n        id_project: projectid,      \n      });\n\n      routerLogger.save(function (err, savedRouterLogger) {        \n        if (err) {\n          winston.error('Error saving RouterLogger ', err)\n        }\n        winston.debug(\"RouterLogger saved \"+ savedRouterLogger);\n        next();\n      });\n      }catch(e) {\n        winston.error('Error saving RouterLogger ', e)\n        next();\n      }\n  });\n\n} else {\n  winston.info(\"RouterLogger disabled \");\n}\n\napp.get('/', function (req, res) {\n  res.send('Hello from Tiledesk server. It\\'s UP. See the documentation here http://developer.tiledesk.com');\n});\n  \n\n\n\nvar projectIdSetter = function (req, res, next) {\n  var projectid = req.params.projectid;\n  winston.debug(\"projectIdSetter projectid: \"+ projectid);\n\n  // if (projectid) {\n    req.projectid = projectid;\n  // }\n  \n  next()\n}\n\n\n\n\nvar projectSetter = function (req, res, next) {\n  var projectid = req.params.projectid;\n  winston.debug(\"projectSetter projectid:\" + projectid);\n\n  if (projectid) {\n    \n    if (!mongoose.Types.ObjectId.isValid(projectid)) {\n      //winston.warn(`Invalid ObjectId: ${projectid}`);\n      return res.status(400).send({ error: \"Invalid project id: \" + projectid });\n    }\n    \n    let q =  Project.findOne({_id: projectid, status: 100});\n    if (cacheEnabler.project) { \n      q.cache(cacheUtil.longTTL, \"projects:id:\"+projectid)  //project_cache\n      winston.debug('project cache enabled');\n    }\n    q.exec(function(err, project){\n      if (err) {\n        //winston.warn(\"Problem getting project with id: \" + projectid + \" req.originalUrl:  \" + req.originalUrl);\n      }\n      winston.debug(\"projectSetter project:\" + project);\n      if (!project) {\n        //winston.warn(\"ProjectSetter project not found with id: \" + projectid);\n        //next();\n        return res.status(400).send({ error: \"Project not found with id: \" + projectid })\n      } else {\n        req.project = project;\n        next(); //call next one time for projectSetter function\n      }\n    \n    });\n  \n  }else {\n    next()\n  }\n  \n\n}\n\n\n// app.use('/admin', admin);\n\n//oauth2\n// app.get('/dialog/authorize', oauth2.authorization);\n// app.post('/dialog/authorize/decision', oauth2.decision);\n// app.post('/oauth/token', oauth2.token);\n\n\n// const ips = ['::1'];\n\napp.use('/troubleshooting', troubleshooting);\napp.use('/auth', auth);\napp.use('/testauth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtest);\n\napp.use('/widgets', widgetsLoader);\napp.use('/w', widgetsLoader);\n\napp.use('/images', images);\napp.use('/files', files);\napp.use('/urls', urls);\napp.use('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], users);\napp.use('/users_util', usersUtil);\napp.use('/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], logs);\napp.use('/requests_util', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], requestUtilRoot);\n\n//app.use('/webhook', webhook); // moved on top before body parser middleware\n\n// TODO security issues\nif (process.env.DISABLE_TRANSCRIPT_VIEW_PAGE ) {\n  winston.info(\" Transcript view page is disabled\");\n}else {\n  app.use('/public/requests', publicRequest);\n}\n\n// project internal auth check. TODO check security issues?\napp.use('/projects',project);\n\nchannelManager.use(app);\n\nif (pubModulesManager) {\n  pubModulesManager.use(app);\n}\n\nif (modulesManager) {\n  modulesManager.use(app);\n}\n\napp.use('/:projectid/', [projectIdSetter, projectSetter, IPFilter.projectIpFilter, IPFilter.projectIpFilterDeny, IPFilter.decodeJwt, IPFilter.projectBanUserFilter]);\n\n\napp.use('/:projectid/authtestWithRoleCheck', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], authtestWithRoleCheck);\n\napp.use('/:projectid/project_users_test', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], project_users_test);\n\napp.use('/:projectid/leads', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], lead);\napp.use('/:projectid/requests/:request_id/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes(null, ['bot','subscription'])] , message);\n\napp.use('/:projectid/messages', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])] , messagesRootRoute);\n\n// department internal auth check\napp.use('/:projectid/departments', department);\n\n\n\n\n\nchannelManager.useUnderProjects(app);\n\napp.use('/:projectid/groups', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], group);\napp.use('/:projectid/tags', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], tag);\napp.use('/:projectid/subscriptions', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], resthook);\n\n//deprecated\napp.use('/:projectid/faq', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);\napp.use('/:projectid/intents', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq);\n\n//Deprecated??\napp.use('/:projectid/faqpub', faqpub);\n\n//deprecated\napp.use('/:projectid/faq_kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);\napp.use('/:projectid/bots', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], faq_kb);\n\n\n\n// app.use('/settings',setting);\n\napp.use('/:projectid/widgets', widgets);\n\n// non mettere ad admin perchà la dashboard  richiama il servizio router.get('/:user_id/:project_id') spesso\n// TOOD security issues. internal route check \n// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);\napp.use('/:projectid/project_users', project_user);\n\n// app.use('/:projectid/project_users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], project_user);\n\n\n//passport double check this and the next\napp.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('guest', ['bot','subscription'])], userRequest);\n\napp.use('/:projectid/requests', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], request);\n\n\napp.use('/:projectid/publicanalytics', publicAnalytics);\n\napp.use('/:projectid/keys', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], key);\n\n//TODO deprecated?\napp.use('/:projectid/jwt', jwtroute);\n\n\napp.use('/:projectid/pendinginvitations', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], pendinginvitation);\napp.use('/:projectid/labels', [fetchLabels],labels);\n\napp.use('/:projectid/campaigns',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], campaigns);\n\napp.use('/:projectid/emails',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], email);\n\napp.use('/:projectid/properties',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], property);\napp.use('/:projectid/segments',[passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], segment);\n\napp.use('/:projectid/llm', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], llm);\napp.use('/:projectid/openai', openai);\napp.use('/:projectid/quotes', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], quotes)\n\napp.use('/:projectid/integration', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], integration )\n\napp.use('/:projectid/mcp', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], mcp);\n\napp.use('/:projectid/kbsettings', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], kbsettings);\napp.use('/:projectid/kb/unanswered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], unanswered);\napp.use('/:projectid/kb/answered', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], answered);\napp.use('/:projectid/kb', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])], kb);\n\napp.use('/:projectid/logs', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], logs);\n\napp.use('/:projectid/webhooks', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], webhooks);\napp.use('/:projectid/copilot', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], copilot);\napp.use('/:projectid/roles', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], roles);\n\napp.use('/:projectid/files', filesp);\n\nif (pubModulesManager) {\n  pubModulesManager.useUnderProjects(app);\n}\n\nif (modulesManager) {\n  modulesManager.useUnderProjects(app);\n}\n \n  \n// REENABLEIT\n// catch 404 and forward to error handler\n// app.use(function (req, res, next) {\n//   var err = new Error('Not Found');\n//   err.status = 404;\n//   next(err);\n// });\n\n/*\napp.use(function (err, req, res, next) {\n  // set locals, only providing error in development\n  res.locals.message = err.message;\n  res.locals.error = req.app.get('env') === 'development' ? err : {};\n\n  // render the error page\n  res.status(err.status || 500);\n  res.render('error');\n});*/\n\n\n\n\n\n// mettere middleware qui per le quote\n\n\n\n// error handler\napp.use((err, req, res, next) => {\n\n  winston.debug(\"err.name\", err.name)\n  if (err.name === \"IpDeniedError\") {\n    winston.debug(\"IpDeniedError\");\n    return res.status(401).json({ err: \"error ip filter\" });\n  }\n\n  const realIp = req.headers['x-forwarded-for']?.split(',')[0] || req.headers['x-real-ip'] || req.ip;\n\n  //emitted by multer when the file is too big\n  if (err.code === \"LIMIT_FILE_SIZE\") {\n    winston.debug(\"LIMIT_FILE_SIZE\");\n    winston.warn(`LIMIT_FILE_SIZE on ${req.originalUrl}`, {\n      limit: process.env.MAX_UPLOAD_FILE_SIZE,\n      ip: req.ip,\n      realIp: realIp\n    });\n    return res.status(413).json({ err: \"Content Too Large\", limit_file_size: process.env.MAX_UPLOAD_FILE_SIZE });\n  }\n  \n  if (err.type === \"entity.too.large\" || err.name === \"PayloadTooLargeError\") {\n    winston.warn(\"Payload too large\", { expected: err.expected, limit: err.limit, length: err.length });\n    return res.status(413).json({ err: \"Request entity too large\", limit: err.limit});\n  }\n\n\n  winston.error(\"General error: \", err);\n  return res.status(500).json({ err: \"error\" });\n});\n\n\n\nmodule.exports = app;"
  },
  {
    "path": "app.json",
    "content": "{\n  \"name\": \"Tiledesk Server\",\n  \"description\": \"Tildesk server\",\n  \"repository\": \"https://github.com/Tiledesk/tiledesk-server/new/master\",\n  \"logo\": \"https://tiledesk.com/tiledesk-logo.png\",\n  \"keywords\": [\"node\", \"express\", \"tiledesk\", \"live chat\"],\n  \"env\": {\n    \"FIREBASE_APIKEY\": {\n      \"description\": \"Firebase API key.\",\n      \"value\": \"\"\n    },\n    \"FIREBASE_AUTHDOMAIN\": {\n      \"description\": \"Firebase Auth domain.\",\n      \"value\": \"\"\n    },\n    \"FIREBASE_DATABASEURL\": {\n      \"description\": \"Firebase database url.\",\n      \"value\": \"\"\n    },\n    \"FIREBASE_STORAGEBUCKET\": {\n      \"description\": \"Firebase storage bucket.\",\n      \"value\": \"\"\n    },\n    \"FIREBASE_MESSAGINGSENDERID\": {\n      \"description\": \"Firebase messaging sender id.\",\n      \"value\": \"\"\n    },    \n     \"FIREBASE_CLIENT_EMAIL\": {\n      \"description\": \"Firebase Client email.\",\n      \"value\": \"\"\n    },\n     \"FIREBASE_PRIVATE_KEY\": {\n      \"description\": \"Firebase private key.\",\n      \"value\": \"\"\n    },\n     \"FIREBASE_PROJECT_ID\": {\n      \"description\": \"Firebase project id.\",\n      \"value\": \"\"\n    },\n    \"CHAT21_ENABLED\": {\n      \"description\": \"Enable or disable Chat21 module.\",\n      \"value\": \"true\"\n    },\n    \"CHAT21_URL\": {\n      \"description\": \"The Chat21 server endpoint.\",\n      \"value\": \"https://us-central1-tiledesk-test.cloudfunctions.net\"\n    },\n    \"CHAT21_APPID\": {\n      \"description\": \"A unique identifier for your chat21 app.\",\n      \"value\": \"myAppId\"\n    },\n    \"WIDGET_LOCATION\": {\n      \"description\": \"Your widget url endpoint\",\n      \"value\": \"http://localhost:4200/\"\n    },\n    \"CHAT21_ADMIN_TOKEN\": {\n      \"description\": \"The Chat21 admin token.\",\n      \"value\": \"youradmintoken\"\n    },\n    \"SUPER_PASSWORD\": {\n      \"description\": \"Super admin password\",\n      \"value\": \"superpassword\"\n    },\n    \"MONGOOSE_AUTOINDEX\": {\n      \"description\": \"Autoindex\",\n      \"value\": true\n    }\n  },\n  \"image\": \"heroku/nodejs\",\n  \"addons\": [\"mongolab\"]\n}\n"
  },
  {
    "path": "archive.sh",
    "content": "#!/bin/bash\n\n# ================================\n# Script interattivo per archiviare branch\n# ================================\n# Usage: ./archive-branch.sh [mode]\n# mode: tag (default) | rename\n# ================================\n\nREMOTE=\"tiledesk-server\"\nMODE=\"${1:-tag}\"   # default mode = tag\n\nPROTECTED_BRANCHES=(\"master\" \"master-PRE\" \"master-COLLAUDO\" \"master-STAGE\")\n\n# Funzione per scegliere un branch valido\nget_branch_to_archive() {\n  local branch=\"$1\"\n\n  while true; do\n    # Se branch protetto, avvisa\n    if [[ \" ${PROTECTED_BRANCHES[@]} \" =~ \" ${branch} \" ]]; then\n      echo \"❌ Il branch '$branch' è protetto e non può essere archiviato.\"\n    fi\n\n    # Controlla che il branch locale esista\n    if git show-ref --verify --quiet refs/heads/\"$branch\"; then\n      break  # branch valido trovato\n    fi\n\n    # Richiedi input all’utente\n    read -p \"Inserisci un branch locale valido da archiviare (oppure 'quit' per annullare): \" branch\n    if [[ \"$branch\" == \"quit\" || \"$branch\" == \"q\" ]]; then\n      echo \"Operazione annullata.\"\n      exit 0\n    fi\n  done\n\n  echo \"$branch\"\n}\n\n# Branch corrente\nCURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)\n\n# Se branch corrente non è protetto, chiedi conferma\nif [[ ! \" ${PROTECTED_BRANCHES[@]} \" =~ \" ${CURRENT_BRANCH} \" ]]; then\n  echo \"⚡ Branch corrente: $CURRENT_BRANCH\"\n  read -p \"Vuoi archiviare questo branch? (y/n): \" CONFIRM\n  if [[ \"$CONFIRM\" != \"y\" ]]; then\n    read -p \"Inserisci il branch da archiviare (oppure 'quit' per annullare): \" USER_BRANCH\n    if [[ \"$USER_BRANCH\" == \"quit\" || \"$USER_BRANCH\" == \"q\" ]]; then\n      echo \"Operazione annullata.\"\n      exit 0\n    fi\n    CURRENT_BRANCH=$(get_branch_to_archive \"$USER_BRANCH\")\n  fi\nelse\n  # Branch corrente è protetto, chiedi un branch alternativo\n  CURRENT_BRANCH=$(get_branch_to_archive \"$CURRENT_BRANCH\")\nfi\n\n# Operazione di archiviazione\nif [ \"$MODE\" == \"tag\" ]; then\n  ARCHIVE_TAG=\"archive/$(echo $CURRENT_BRANCH | tr '/' '-')\"\n  echo \"Creando tag di archivio: $ARCHIVE_TAG\"\n  git tag \"$ARCHIVE_TAG\" \"$CURRENT_BRANCH\"\n  git push \"$REMOTE\" \"$ARCHIVE_TAG\"\n\n  echo \"Eliminando branch remoto $CURRENT_BRANCH\"\n  git push \"$REMOTE\" --delete \"$CURRENT_BRANCH\"\n\n  echo \"Eliminando branch locale $CURRENT_BRANCH\"\n  git branch -d \"$CURRENT_BRANCH\"\n\n  echo \"✅ Branch '$CURRENT_BRANCH' archiviato con tag '$ARCHIVE_TAG'\"\n\nelif [ \"$MODE\" == \"rename\" ]; then\n  ARCHIVE_BRANCH=\"archive/$CURRENT_BRANCH\"\n  echo \"Rinomino branch locale in: $ARCHIVE_BRANCH\"\n  git branch -m \"$CURRENT_BRANCH\" \"$ARCHIVE_BRANCH\"\n\n  echo \"Pusho nuovo branch su remote $REMOTE\"\n  git push \"$REMOTE\" \"$ARCHIVE_BRANCH\"\n\n  echo \"Eliminando branch remoto vecchio $CURRENT_BRANCH\"\n  git push \"$REMOTE\" --delete \"$CURRENT_BRANCH\"\n\n  echo \"✅ Branch '$CURRENT_BRANCH' archiviato come '$ARCHIVE_BRANCH'\"\n\nelse\n  echo \"❌ Modalità non valida. Usa 'tag' o 'rename'.\"\n  exit 1\nfi"
  },
  {
    "path": "bin/www",
    "content": "#!/usr/bin/env node\n\n/**\n * Module dependencies.\n */\n\nvar app = require('../app');\nvar debug = require('debug')('tiledesk-server:server');\nvar http = require('http');\nvar version = require('../package.json').version\nvar mongoose = require('mongoose');\n\n/**\n * Get port from environment and store in Express.\n */\n\nvar port = normalizePort(process.env.PORT || '3000');\napp.set('port', port);\n\n/**\n * Create HTTP server.\n */\n\n\nvar httpServer = http.createServer(app);\n\nvar webSocketServer = require('../websocket/webSocketServer');\nwebSocketServer.init(httpServer);\n\n\n/**\n * Listen on provided port, on all network interfaces.\n */\n\n// use ipv4 or ipv6 https://stackoverflow.com/questions/50855419/get-only-ipv4-ips-via-nodejs-express\n// var listener = httpServer.listen(port,'0.0.0.0', function(){      \n\nvar listener = httpServer.listen(port, function(){      \n    console.log('Listening tiledesk-server ver:'+version+' on port ' + listener.address().port); //Listening on port 8888\n});\n\nhttpServer.on('error', onError);\nhttpServer.on('listening', onListening);\n\n/**\n * Normalize a port into a number, string, or false.\n */\n\nfunction normalizePort(val) {\n  var port = parseInt(val, 10);\n\n  if (isNaN(port)) {\n    // named pipe\n    return val;\n  }\n\n  if (port >= 0) {\n    // port number\n    return port;\n  }\n\n  return false;\n}\n\n/**\n * Event listener for HTTP server \"error\" event.\n */\n\nfunction onError(error) {\n  if (error.syscall !== 'listen') {\n    throw error;\n  }\n\n  var bind = typeof port === 'string'\n    ? 'Pipe ' + port\n    : 'Port ' + port;\n\n  // handle specific listen errors with friendly messages\n  switch (error.code) {\n    case 'EACCES':\n      console.error(bind + ' requires elevated privileges');\n      process.exit(1);\n      break;\n    case 'EADDRINUSE':\n      console.error(bind + ' is already in use');\n      process.exit(1);\n      break;\n    default:\n      throw error;\n  }\n}\n\n/**\n * Event listener for HTTP server \"listening\" event.\n */\n\nfunction onListening() {\n  var addr = httpServer.address();\n  var bind = typeof addr === 'string'\n    ? 'pipe ' + addr\n    : 'port ' + addr.port;\n  debug('Listening on ' + bind);\n}\n"
  },
  {
    "path": "channels/channelManager.js",
    "content": "\nvar winston = require('../config/winston');\n\n\nvar chat21Enabled = process.env.CHAT21_ENABLED;\nwinston.debug(\"chat21Enabled: \"+chat21Enabled);\n\nvar engine = process.env.CHAT21_ENGINE;\nwinston.debug(\"chat21 engine: \"+engine);\n\n\nvar validtoken = require('../middleware/valid-token');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\n\n\nif (chat21Enabled && chat21Enabled == \"true\") {\n    winston.info(\"ChannelManager - Chat21 channel is enabled\");\n}else {\n    winston.warn(\"ChannelManager Chat21 channel is disabled. Attention!!\");\n}\n\nclass ChannelManager {\n\n    use(app) {\n        var that = this;\n        winston.debug(\"ChannelManager using controllers\");\n\n        if (chat21Enabled && chat21Enabled == \"true\") {\n\n            var chat21WebHook = require('../channels/chat21/chat21WebHook');\n            app.use('/chat21/requests',  chat21WebHook); //<- TODO cambiare /request in /webhook\n\n            var chat21Contact = require('../channels/chat21/chat21Contact');\n            app.use('/chat21/contacts',  chat21Contact);\n\n            var chat21ConfigRoute = require('../channels/chat21/configRoute');\n            app.use('/chat21/config',  chat21ConfigRoute);\n\n            \n            if (engine && engine==\"firebase\") {\n                winston.info(\"ChannelManager - Chat21 channel engine is firebase\");\n                var firebaseAuth = require('../channels/chat21/firebaseauth');\n                app.use('/chat21/firebase/auth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], firebaseAuth);\n            } else { //if (engine && engine==\"native\") {\n                winston.info(\"ChannelManager - Chat21 channel engine is native mqtt\");\n                var nativeAuth = require('../channels/chat21/nativeauth');\n                app.use('/chat21/native/auth', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], nativeAuth);\n            }\n            winston.info(\"ChannelManager - Chat21 channel routes initialized\");\n        } else {\n            winston.info(\"ChannelManager - Chat21 channel routes not initialized.\");\n        }            \n\n        \n    }\n\n    useUnderProjects(app) {\n\n    }\n\n    listen() {\n        var that = this;\n\n        if (process.env.NODE_ENV == 'test')  {\t\n            return winston.info(\"ChannelManager listener disabled for testing\");\n        }\n        \n        if (chat21Enabled && chat21Enabled == \"true\") {   \n            var chat21Handler = require('../channels/chat21/chat21Handler');         \n            chat21Handler.listen();\n            winston.info(\"ChannelManager listener started\");\n        }else {\n            winston.info(\"ChannelManager listener NOT started \");\n        }\n    }\n\n\n    \n}\n\nvar channelManager = new ChannelManager();\nmodule.exports = channelManager;\n"
  },
  {
    "path": "channels/chat21/chat21Client.js",
    "content": "\nvar chat21Config = require('../../channels/chat21/chat21Config');\nvar Chat21 = require('@chat21/chat21-node-sdk');\nvar winston = require('../../config/winston');\n\n\n\nvar url = process.env.CHAT21_URL || chat21Config.url;\nvar appid = process.env.CHAT21_APPID || chat21Config.appid;\n\nwinston.info('Chat21Client chat21.url: '+url );\nwinston.info('Chat21Client chat21.appid: '+ appid);\n\nvar chat21 = new Chat21({\n url: url,\n appid: appid\n //authurl: process.env.CHAT21_AUTH_URL\n});\n\nmodule.exports = chat21;\n"
  },
  {
    "path": "channels/chat21/chat21Config.js",
    "content": "module.exports = {\n  'url': 'https://CHANGEIT.cloudfunctions.net',\n  'appid': 'tilechat',\n  'adminToken' : 'chat21-secret-orgAa,'\n};\n"
  },
  {
    "path": "channels/chat21/chat21Contact.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar mongoose = require('mongoose');\n\nvar Project_user = require(\"../../models/project_user\");\n\nvar winston = require('../../config/winston');\n\n// THE THREE FOLLOWS IMPORTS  ARE USED FOR AUTHENTICATION IN THE ROUTE\nvar passport = require('passport');\nrequire('../../middleware/passport')(passport);\nvar validtoken = require('../../middleware/valid-token')\nvar RoleConstants = require(\"../../models/roleConstants\");\nvar cacheUtil = require('../../utils/cacheUtil');\nconst { Query } = require('mongoose');\n\n\nrouter.get('/:contact_id', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], async (req, res) => {\n  winston.debug('REQ USER ID ', req.user._id);\n  \n  var isObjectId = mongoose.Types.ObjectId.isValid(req.user._id);\n  winston.debug(\"isObjectId:\"+ isObjectId);     \n\n  var query = { role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" };\n  winston.debug(' query: ',query);\n\n\n  if (isObjectId) {\n    query.id_user = req.user._id;\n    // query.id_user = mongoose.Types.ObjectId(contact_id);\n  } else {\n    query.uuid_user = req.user._id;\n  }\n\n  var projects = await Project_user.find(query).    \n    exec(); \n\n  var projectsArray = [];\n  projects.forEach(project => {\n    projectsArray.push(project.id_project);\n    // projectsArray.push(mongoose.Types.ObjectId(project.id_project));\n  });\n    \n\n  var contact_id = req.params.contact_id;\n  winston.debug('contact_id: '+ contact_id);\n\n  isObjectId = mongoose.Types.ObjectId.isValid(contact_id);\n  winston.debug(\"isObjectId:\"+ isObjectId);                             \n\n  query = { id_project: { $in : projectsArray }, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" };\n  winston.debug(' query: ',query);\n\n  if (isObjectId) {\n    query.id_user = contact_id;\n    // query.id_user = mongoose.Types.ObjectId(contact_id);\n  } else {\n    query.uuid_user = contact_id;\n  }\n\n  winston.debug(\"query: \", query);\n\n  var teammates = await Project_user.find(query).\n  populate('id_project').\n  populate('id_user').\n  exec(); \n\n  var result = [];\n  if (teammates && teammates.length>0) {  \n    teammates.forEach(teammate => {\n      \n      var contact = {};\n      if (teammate.id_user) {\n        contact.uid = teammate.id_user._id;\n        contact.email = teammate.id_user.email;\n        contact.firstname = teammate.id_user.firstname;\n        contact.lastname = teammate.id_user.lastname;\n\n        if (teammate.id_project) {\n          contact.description =  teammate.id_project.name;\n        }\n\n        // if (teammate.id_user.createdAt) {\n        //   contact.timestamp = teammate.id_user.createdAt.getTime();\n        // }\n        \n        // winston.info(\"teammate: \"+ JSON.stringify(teammate));\n\n        var contactFound = result.filter(c => c.uid === contact.uid );\n        winston.debug(\"contactFound: \"+ JSON.stringify(contactFound));\n\n        // var index = result.indexOf(contactFound);\n        let index = result.findIndex(c => c.uid === contact.uid );\n\n        winston.debug(\"index: \"+ index);\n\n        if (contactFound.length==0) {\n          winston.debug(\"not found\");\n          result.push(contact);\n        }else {\n          winston.debug(\"found\",contactFound);\n          // contactFound[0].description = \"sssss\";\n          contactFound[0].description= contactFound[0].description + \", \"+teammate.id_project.name;\n          result[index] = contactFound[0];\n          \n        }\n      }\n      \n\n    });\n  }\n\n  winston.debug(\"send\");\n\n  if (result && result.length>0) {\n    res.json(result[0]);\n  }else {\n    res.json({});\n  }\n  \n    \n  \n});\n\n\n\n\nrouter.get('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], async (req, res) => {\n  winston.debug('REQ USER ID ', req.user._id);\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  } \n  winston.debug(\"direction\",direction);\n\n  var sortField = \"updatedAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  } \n  winston.debug(\"sortField\",sortField);\n\n  var sortQuery={};\n  sortQuery[sortField] = direction;\n\n  var query = { role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" };\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(req.user._id);\n  winston.debug(\"isObjectId:\"+ isObjectId);                             \n\n  if (isObjectId) {\n    query.id_user = req.user._id;\n    // query.id_user = mongoose.Types.ObjectId(contact_id);\n  } else {\n    query.uuid_user = req.user._id;\n  }\n\n\n\n  var projects = await Project_user.find(query).    \n    exec(); \n\n    var projectsArray = [];\n    projects.forEach(project => {\n      projectsArray.push(project.id_project);\n    });\n    \n    var query = { id_project: { $in : projectsArray }, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" };\n    winston.debug(\"query: \", query);\n\n    var teammates = await Project_user.find(query).\n    populate('id_project').\n    populate('id_user').\n    sort(sortQuery).\n    exec(); \n\n    var result = [];\n\n    if (teammates && teammates.length>0) {  \n      teammates.forEach(teammate => {\n        \n        var contact = {};\n        if (teammate.id_user) {\n          contact.uid = teammate.id_user._id;\n          contact.email = teammate.id_user.email;\n          contact.firstname = teammate.id_user.firstname;\n          contact.lastname = teammate.id_user.lastname;\n\n          if (teammate.id_project) {\n            contact.description =  teammate.id_project.name;\n          }\n\n          // if (teammate.id_user.createdAt) {\n          //   contact.timestamp = teammate.id_user.createdAt.getTime();\n          // }\n          \n          // winston.info(\"teammate: \"+ JSON.stringify(teammate));\n\n          var contactFound = result.filter(c => c.uid === contact.uid );\n          winston.debug(\"contactFound: \"+ JSON.stringify(contactFound));\n\n          // var index = result.indexOf(contactFound);\n          let index = result.findIndex(c => c.uid === contact.uid );\n\n          winston.debug(\"index: \"+ index);\n\n          if (contactFound.length==0) {\n            winston.debug(\"not found\");\n            result.push(contact);\n          }else {\n            winston.debug(\"found\",contactFound);\n            // contactFound[0].description = \"sssss\";\n            contactFound[0].description= contactFound[0].description + \", \"+teammate.id_project.name;\n            result[index] = contactFound[0];\n            \n          }\n        }\n        \n\n      });\n    }\n    winston.debug(\"send\");\n    res.json(result);\n    \n  \n});\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "channels/chat21/chat21Event.js",
    "content": "const EventEmitter = require('events');\n\nclass Chat21Event extends EventEmitter {}\n\nconst chat21Event = new Chat21Event();\n\n\n\n\nmodule.exports = chat21Event;\n"
  },
  {
    "path": "channels/chat21/chat21Handler.js",
    "content": "\nconst messageEvent = require('../../event/messageEvent');\nconst authEvent = require('../../event/authEvent');\nconst botEvent = require('../../event/botEvent');\nconst requestEvent = require('../../event/requestEvent');\nconst groupEvent = require('../../event/groupEvent');\nconst chat21Event = require('./chat21Event');\nconst leadEvent = require('../../event/leadEvent');\n\nvar messageService = require('../../services/messageService');\nvar MessageConstants = require(\"../../models/messageConstants\");\nvar ChannelConstants = require(\"../../models/channelConstants\");\nvar winston = require('../../config/winston');\nvar Request = require(\"../../models/request\");\nvar chat21Config = require('./chat21Config');\nvar chat21 = require('./chat21Client');\n\n\n\n\n\nconst MaskData = require(\"maskdata\");\n\nconst maskPasswordOptions = {\n    // Character to mask the data. default value is '*'\n    maskWith : \"*\",\n    //Should be positive Integer\n    // If the starting 'n' digits needs to be unmasked\n    // Default value is 4\n    unmaskedStartDigits: 3, \n    \n    // Should be positive Integer\n    //If the ending 'n' digits needs to be unmasked\n    // Default value is 1\n    unmaskedEndDigits: 2\n  };\n\n\n\n\n// var chat21Util = require('./chat21Util');\n// var tiledeskUtil = require('./tiledesk-util');\n\nvar adminToken =  process.env.CHAT21_ADMIN_TOKEN || chat21Config.adminToken;\n\nconst masked_adminToken = MaskData.maskPhone(adminToken, maskPasswordOptions);\n\nwinston.info('Chat21Handler adminToken: '+ masked_adminToken);\n\n\n\n\nclass Chat21Handler {\n\n \n    typing(message, timestamp) {\n        return new Promise(function (resolve, reject) {\n\n            // if privateFor skip typing\n            //no typing for subtype info\n            if (message.attributes && message.attributes.subtype && (message.attributes.subtype==='info' || message.attributes.subtype==='private')) {\n                return resolve();\n            }else {\n                chat21.conversations.typing(message.recipient, message.sender, message.text, timestamp).finally(function() {\n                    return resolve();\n                });\n            }\n            \n\n        });\n    }\n\n\n    listen() {\n\n        var that = this;       \n       \n        winston.debug(\"Chat21Handler listener start \");\n        \n\n        if (process.env.SYNC_CHAT21_GROUPS !==\"true\") {\n            winston.info(\"Sync Tiledesk to Chat21 groups disabled\");\n            // return; questo distrugge il tread. attento non lo mettere +\n        }\n\n        \n      \n        // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler\n\n        // su projectUser create e update\n        authEvent.on('user.signup', function(userData) {\n            var firstName = userData.savedUser.firstname;\n            var lastName = userData.savedUser.lastname;\n            var email = userData.savedUser.email;\n            var current_user = userData.savedUser.id;\n\n            setImmediate(() => {\n                winston.debug(\"Chat21Handler on user.signup \",  userData);\n\n                chat21.auth.setAdminToken(adminToken);\n\n                // create: function(firstname, lastname, email, current_user){\n                chat21.contacts.create(firstName, lastName, email, current_user).then(function(data) {\n                    winston.verbose(\"Chat21 contact created: \" + JSON.stringify(data));      \n                    chat21Event.emit('contact.create', data);                                          \n                }).catch(function(err) {\n                    winston.error(\"Error creating chat21 contact \", err);\n                    chat21Event.emit('contact.create.error', err);\n                });\n\n            });\n        });\n\n\n        authEvent.on('user.update', function(userData) {\n            var firstName = userData.updatedUser.firstname;\n            var lastName = userData.updatedUser.lastname;            \n            var current_user = userData.updatedUser.id;\n\n            setImmediate(() => {\n                winston.debug(\"Chat21Handler on user.update \",  userData);\n\n                chat21.auth.setAdminToken(adminToken);\n\n                // update: function(firstname, lastname, current_user){\n                chat21.contacts.update(firstName, lastName, current_user).then(function(data) {\n                    winston.verbose(\"Chat21 contact updated: \" + JSON.stringify(data));      \n                    chat21Event.emit('contact.update', data);                                          \n                }).catch(function(err) {\n                    winston.error(\"Error updating chat21 contact \", err);\n                    chat21Event.emit('contact.update.error', err);\n                });\n\n            });\n        });\n\n\n        botEvent.on('faqbot.create', function(bot) {\n            var firstName = bot.name;\n            var lastName = \"\";\n            var email = \"\";\n            // botprefix\n            var current_user = \"bot_\"+bot.id;\n\n            setImmediate(() => {\n                winston.debug(\"Chat21Handler on faqbot.create \",  bot);\n\n                chat21.auth.setAdminToken(adminToken);\n\n                // create: function(firstname, lastname, email, current_user){\n                chat21.contacts.create(firstName, lastName, email, current_user).then(function(data) {                    \n                    winston.verbose(\"Chat21 contact created: \" + JSON.stringify(data));         \n                    chat21Event.emit('contact.create', data);                                          \n                }).catch(function(err) {\n                    winston.error(\"Error creating chat21 contact \", err);\n                    chat21Event.emit('contact.create.error', err);\n                });\n\n            });\n        });\n\n\n\n        botEvent.on('faqbot.update', function(bot) {\n            var firstName = bot.name;\n            var lastName = \"\";\n            // botprefix\n            var current_user = \"bot_\"+bot.id;\n\n            setImmediate(() => {\n                winston.debug(\"Chat21Handler on faqbot.create \",  bot);\n\n                chat21.auth.setAdminToken(adminToken);\n\n               // update: function(firstname, lastname, current_user){\n                chat21.contacts.update(firstName, lastName, current_user).then(function(data) {\n                    winston.verbose(\"Chat21 contact updated: \" + JSON.stringify(data));      \n                    chat21Event.emit('contact.update', data);                                          \n                }).catch(function(err) {\n                    winston.error(\"Error updating chat21 contact \", err);\n                    chat21Event.emit('contact.update.error', err);\n                });\n\n            });\n        });\n\n\n    // quando passa da lead temp a default aggiorna tutti va bene?        \n         leadEvent.on('lead.update', function(lead) {\n            //  non sembra funzionare chiedi a Dario dove prende le info\n            setImmediate(() => {\n                winston.debug(\"Chat21Handler on lead.update \",  lead);\n\n                //  TODO AGGIORNA SOLO SE PASSA DA GUEST A ALTRO??\n                Request.find({lead: lead._id, id_project: lead.id_project}, function(err, requests) {\n\n                    if (err) {\n                        winston.error(\"Error getting request by lead\", err);\n                        return 0;\n                    }\n                    if (!requests || (requests && requests.length==0)) {\n                        winston.verbose(\"No request found for lead id \" +lead._id );\n                        return 0;\n                    }\n                    \n                    chat21.auth.setAdminToken(adminToken);\n\n                    requests.forEach(function(request) {\n                        if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                            winston.verbose(\"Chat21Handler lead.update for request \",  request);\n                            \n                            var groupName = lead.fullname;\n                            if (request.subject) {\n                                groupName=request.subject;\n                            }\n                            // update: function(name, owner, attributes, group_id){\n\n                            chat21.groups.update(groupName, undefined, undefined, request.request_id).then(function(data) {\n                                winston.verbose(\"Chat21 group updated for lead.update: \" + JSON.stringify(data));      \n                                chat21Event.emit('group.update', data);                                          \n                            }).catch(function(err) {\n                                winston.error(\"Error updating chat21 group for lead.update\", err);\n                                chat21Event.emit('group.update.error', err);\n                            });\n\n                             // updateAttributes: function(attributes, group_id){\n                                 var gattributes = {userFullname:lead.fullname, userEmail: lead.email }\n                                //  qui1\n                            chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {\n                                winston.verbose(\"Chat21 group gattributes for lead.update updated: \" + JSON.stringify(data));      \n                                chat21Event.emit('group.update', data);        \n                                chat21Event.emit('group.attributes.update', data);                                          \n                            }).catch(function(err) {\n                                winston.error(\"Error updating chat21 gattributes for lead.update group \", err);\n                                chat21Event.emit('group.attributes.update.error', err);\n                            });\n\n\n                        }\n                    })\n                  \n                });\n\n              \n            });\n        });\n\n\n        messageEvent.on('message.test', function(message) {\n        \n            winston.info(\"Chat21Sender message.test\");\n\n            chat21.auth.setAdminToken(adminToken);\n\n            return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, \n                message.recipient_fullname, message.text, message.sender, message.attributes, message.type, message.metadata, message.timestamp, message.group)\n                        .then(function(data){\n                            winston.info(\"Chat21Sender sendToGroup test: \"+ JSON.stringify(data));\n                        });\n        });\n       \n        // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler\n        messageEvent.on('message.sending', function(message) {\n\n            // setImmediate(() => {\n                // TODO perche nn c'è setImmedite? per performace\n\n\n                    winston.verbose(\"Chat21Sender on message.sending: \"+  message.text);\n                    winston.debug(\"Chat21Sender on message.sending \",  message);\n                   \n                   if (message && \n                    message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING &&\n                    message.channel_type ==  MessageConstants.CHANNEL_TYPE.GROUP &&\n                    message.request && \n                    message.request.channelOutbound.name == ChannelConstants.CHAT21) { //here only request.channelOutbound is important because chat21handler is for outgoing messages( from Tiledesk to agents clients)\n\n                    \n                        chat21.auth.setAdminToken(adminToken);\n\n\n                        //chat21.conversations.typing(message.recipient, message.sender, message.text, new Date()).finally(function() {\n                        return that.typing(message,new Date() ).then(function() {\n                       \n\n                        let attributes = message.attributes;\n\n                        if (!attributes) attributes = {};\n                        \n                        attributes['tiledesk_message_id'] = message._id;\n\n                        attributes['projectId'] = message.id_project; //TODO not used. used by ionic to open request detail ???\n                        \n                        if (message.channel && message.channel.name) {\n                            attributes['channel'] = message.channel.name;\n                        }\n                        \n                       \n\n\n                        winston.verbose(\"Chat21Sender sending message.sending: \"+  message.text);\n                        winston.debug(\"Chat21Sender sending message.sending \",  message);\n\n                        // chat21Util.getParsedMessage().then(function(messageData) {\n                        //     message = messageData;\n\n                            // doent'work must merge older field with new message = chat21Util.parseReply(message.text);\n\n                            // sendToGroup: function(sender_fullname, recipient_id, recipient_fullname, text, sender_id, attributes, type, metadata, timestamp){\n\n\n                            var timestamp = Date.now();\n                            // var timestamp = undefined;\n                            if (message.attributes && message.attributes.clienttimestamp) {\n                                timestamp = message.attributes.clienttimestamp;\n                            }\n\n                            var recipient_fullname = \"Guest\"; \n                            // guest_here\n                        \n                            if (message.request && message.request.lead && message.request.lead.fullname) {\n                                recipient_fullname = message.request.lead.fullname;\n                            }\n                            if (message.request && message.request.subject ) {\n                                recipient_fullname = message.request.subject;\n                            }\n                            if (message.request && message.request.channel  && message.request.channel.name ) {\n                                attributes['request_channel'] = message.request.channel.name;\n                            }\n\n                            /*\n                            const parsedReply = tiledeskUtil.parseReply(message.text);\n                            winston.info(\"Chat21 sendToGroup parsedMessage \" + JSON.stringify(parsedReply));\n\n                            // message = {...message, ...parsedReply.message };\n                            // merge(message, parsedReply.message );\n\n                            if (parsedReply.message.text) {\n                                message.text = parsedReply.message.text;\n                            }\n                            if (parsedReply.message.type) {\n                                message.type = parsedReply.message.type;\n                            }\n                            if (parsedReply.message.type) {\n                                message.metadata = parsedReply.message.metadata;\n                            }\n                            \n                            // var msg_attributes = {...message.attributes, ...parsedReply.message.attributes };\n                            if (parsedReply.message && parsedReply.message.attributes) {\n                                for(const [key, value] of Object.entries(parsedReply.message.attributes)) {\n                                    attributes[key] = value\n                                }\n                            }    \n                            */   \n                         \n                            // performance console log\n                            // console.log(\"************* send message chat21: \"+new Date().toISOString(), message.text );\n\n                           return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, \n                                recipient_fullname, message.text, message.sender, attributes, message.type, message.metadata, timestamp)\n                                        .then(function(data){\n                                            winston.verbose(\"Chat21Sender sendToGroup sent: \"+ JSON.stringify(data) + \" for text message \" + message.text);\n                                    \n\n                                            // chat21.conversations.stopTyping(message.recipient,message.sender);\n                                            \n                                            \n                                            // performance console log\n                                            // console.log(\"************* senttt message chat21: \"+new Date().toISOString(), message.text );\n\n                                            chat21Event.emit('message.sent', data);\n    \n                                                winston.debug(\"Message changeStatus 1\");\n                                                messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){\n                                                    winston.debug(\"Chat21 message sent \", upMessage.toObject());                                        \n                                                }).catch(function(err) {\n                                                    winston.error(\"Error Chat21 message sent with id: \"+message._id, err);                                        \n                                                });\n    \n                                }).catch(function(err) {\n                                    winston.error(\"Chat21 sendToGroup err\", err);\n                                    chat21Event.emit('message.sent.error', err);\n                                });\n\n                            });\n                        \n                        // });\n                    }\n                    else if (message &&\n                         message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING && \n                         message.channel_type ==  MessageConstants.CHANNEL_TYPE.DIRECT &&\n                         message.channel.name == ChannelConstants.CHAT21) {\n                        \n                            // winston.warn(\"Chat21Sender this is a direct message. Unimplemented method\", message);\n\n                            chat21.auth.setAdminToken(adminToken);\n\n                            winston.debug(\"Chat21Sender\");\n                            // send: function(sender_fullname, recipient_id, recipient_fullname, text, sender_id, attributes, type, metadata){\n                           return  chat21.messages.send(message.senderFullname,     message.recipient, \n                            message.recipientFullname, message.text, message.sender, message.attributes, message.type, message.metadata)\n                                    .then(function(data){\n                                        winston.verbose(\"Chat21Sender send sent: \"+ JSON.stringify(data));\n                                \n\n                                        // chat21.conversations.stopTyping(message.recipient,message.sender);\n\n                                        chat21Event.emit('message.sent', data);\n                                            winston.debug(\"Message changeStatus 2\");\n                                            messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){\n                                                winston.debug(\"Chat21 message sent \", upMessage.toObject());                                        \n                                            }).catch(function(err) {\n                                                winston.error(\"Error Chat21 message sent with id: \"+message._id, err);                                        \n                                            });\n\n                            }).catch(function(err) {\n                                winston.error(\"Chat21 send direct err\", err);\n                                chat21Event.emit('message.sent.error', err);\n                            });\n\n                            \n\n                    } \n                    \n                    else if (message &&\n                        message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING && \n                        message.channel_type ==  MessageConstants.CHANNEL_TYPE.GROUP &&\n                        message.channel.name == ChannelConstants.CHAT21) {\n                       \n                           // winston.warn(\"Chat21Sender this is a group message. Unimplemented method\", message);\n\n                           chat21.auth.setAdminToken(adminToken);\n\n                           var timestamp = Date.now();\n                           // var timestamp = undefined;\n                           if (message.attributes && message.attributes.clienttimestamp) {\n                               timestamp = message.attributes.clienttimestamp;\n                           }\n\n\n                           return  chat21.messages.sendToGroup(message.senderFullname,     message.recipient, \n                            message.recipientFullname, message.text, message.sender, message.attributes, message.type, message.metadata, timestamp)                         \n                                   .then(function(data){\n                                       winston.verbose(\"Chat21Sender send sent: \"+ JSON.stringify(data));\n                               \n\n                                       // chat21.conversations.stopTyping(message.recipient,message.sender);\n\n                                       chat21Event.emit('message.sent', data);\n                                       winston.debug(\"Message changeStatus 3\");\n                                           messageService.changeStatus(message._id, MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) .then(function(upMessage){\n                                               winston.debug(\"Chat21 message sent \", upMessage.toObject());                                        \n                                           }).catch(function(err) {\n                                               winston.error(\"Error Chat21 message sent with id: \"+message._id, err);                                        \n                                           });\n\n                           }).catch(function(err) {\n                               winston.error(\"Chat21 sendToGroup err\", err);\n                               chat21Event.emit('message.sent.error', err);\n                           });\n\n                           \n\n                   } else {\n                        winston.error(\"Chat21Sender this is not a group o direct message\", message);\n                        return;\n                    }\n                // });\n            });\n\n\n            requestEvent.on('request.attributes.update',  function(request) {          \n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                        var gattributes = request.attributes;\n                        // qui1\n                        chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {\n                            winston.verbose(\"Chat21 group gattributes for request.attributes.update updated: \" + JSON.stringify(data));      \n                            chat21Event.emit('group.update', data);        \n                            chat21Event.emit('group.attributes.update', data);                                          \n                        }).catch(function(err) {\n                            winston.error(\"Error updating chat21 gattributes for request.attributes.update group \", err);\n                            chat21Event.emit('group.attributes.update.error', err);\n                        });\n\n                    }\n                });\n            });\n\n            \n            //   not used now. Before used by ionic\n            // requestEvent.on('request.update',  function(request) {          \n\n            //     setImmediate(() => {\n            //         if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n            //             chat21.auth.setAdminToken(adminToken);\n\n            //               // https://stackoverflow.com/questions/42310950/handling-undefined-values-with-firebase/42315610                        \n            //             //   var requestWithoutUndefined = JSON.parse(JSON.stringify(request, function(k, v) {\n            //             //     if (v === undefined) { return null; } return v; \n            //             //  }));\n\n            //             // var gattributes = { \"_request\":requestWithoutUndefined};\n\n            //             // qui1\n            //             chat21.groups.updateAttributes(gattributes, request.request_id).then(function(data) {\n            //                 winston.verbose(\"Chat21 group gattributes for request.update updated: \" +  JSON.stringify(data));      \n            //                 chat21Event.emit('group.update', data);        \n            //                 chat21Event.emit('group.attributes.update', data);                                          \n            //             }).catch(function(err) {\n            //                 winston.error(\"Error updating chat21 gattributes for request.update group \", err);\n            //                 chat21Event.emit('group.attributes.update.error', err);\n            //             });\n\n            //         }\n            //     });\n            // });\n\n\n            // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler\n            requestEvent.on('request.create',  function(request) {          \n\n                winston.debug(\"chat21Handler requestEvent request.create called\" , request);\n                // setImmediate(() => {\n// perche nn c'è setImmedite? per performace\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                        \n\n                        // let requestObj = request.toObject();\n                        let requestObj = request.toJSON();\n                        \n                        winston.verbose(\"creating chat21 group for request with id: \" + requestObj._id);\n\n                        // winston.info(\"requestObj.participants: \"+ Object.prototype.toString.call(requestObj.participants));\n                        winston.debug(\"requestObj.participants: \"+ JSON.stringify(requestObj.participants));\n                        \n                        let members = requestObj.participants;\n                        // var members = reqParticipantArray;\n\n                        members.push(\"system\");\n                        if (request.lead) {\n                            // lead_id used. Change it?\n                            members.push(request.lead.lead_id);\n                        }\n                        \n                        \n                        // let membersArray = JSON.parse(JSON.stringify(members));\n                        // winston.info(\"membersArray\", membersArray);\n\n                        var gAttributes = requestObj.attributes || {};\n                        // TODO ATTENTION change value by reference\n                        // var gAttributes = request.attributes || {}; //BUG LINK TO event emmiter obiect\n\n\n                        // problema requester_id\n                        gAttributes[\"requester_id\"] = request.requester_id;\n                    \n                       \n                        gAttributes['projectId'] = request.id_project; //used by ionic to open request detail \n\n                        if (request.lead) {\n                            gAttributes['userFullname'] = request.lead.fullname; //used by ionic to open request detail \n                            gAttributes['userEmail'] = request.lead.email; //used by ionic to open request detail \n                            // TOOD is it necessary? \n                            // lead_id used. Change it?\n                            gAttributes['senderAuthInfo'] = {authType: \"USER\", authVar: {uid:request.lead.lead_id}}; //used by ionic otherwise ionic dont show userFullname in the participants panel\n                        }\n                        // TODO ionic dont show attributes panel if attributes.client is empty. bug?\n                        gAttributes['client'] = request.userAgent || 'n.d.'; //used by ionic to open request detail \n                        if (request.department) {\n                            gAttributes['departmentId'] = request.department._id; //used by ionic to open request detail \n                            gAttributes['departmentName'] = request.department.name; //used by ionic to open request detail \n                        }                        \n                        gAttributes['sourcePage'] = request.sourcePage; //used by ionic to open request detail \n\n                        \n                        // https://stackoverflow.com/questions/42310950/handling-undefined-values-with-firebase/42315610\n\n                        //   not used now. Before used by ionic\n                        // var requestWithoutUndefined = JSON.parse(JSON.stringify(request, function(k, v) {\n                        //     if (v === undefined) { return null; } return v; \n                        //  }));\n                        //  gAttributes['_request'] = requestWithoutUndefined; //used by ionic to open request detail \n                        \n                        \n\n\n \n                        winston.debug(\"Chat21 group create gAttributes: \",gAttributes);  \n\n                        var groupId = request.request_id;\n\n                        var group_name = \"Guest\"; \n                        // guest_here\n                        \n                        if (request.lead && request.lead.fullname) {\n                            group_name = request.lead.fullname;\n                        }\n                        if (request.subject) {\n                            group_name = request.subject;\n                        }\n                        \n                        // performance console log\n                        // console.log(\"************* before request.support_group.created: \"+new Date().toISOString());\n\n                        //TODO racecondition?\n                        return chat21.groups.create(group_name, members, gAttributes, groupId).then(function(data) {\n                                winston.verbose(\"Chat21 group created: \" + JSON.stringify(data));     \n\n                                // performance console log\n                                // console.log(\"************* after request.support_group.created: \"+new Date().toISOString()); \n\n                                requestEvent.emit('request.support_group.created', request);\n\n                                chat21Event.emit('group.create', data);      \n                            }).catch(function(err) {\n                                winston.error(\"Error creating chat21 group \", err);\n\n                                requestEvent.emit('request.support_group.created.error', request);\n\n                                chat21Event.emit('group.create.error', err);\n                            });\n\n\n                    }\n                // });\n            });\n    \n\n            // chat21Handler on worker is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler\n\n            requestEvent.on('request.close',  function(request) {          //request.close event here noqueued\n                winston.debug(\"request.close event here 8\")\n                winston.debug(\"chat21Handler requestEvent request.close called\" , request);\n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);                      \n\n                        winston.debug(\"Chat21Sender archiving conversations for \",request.participants);\n\n                       //iterate request.participant and archive conversation\n                       request.participants.forEach(function(participant,index) {\n\n                        winston.debug(\"Chat21Sender archiving conversation: \" + request.request_id + \"for \" + participant);\n\n                            chat21.conversations.archive(request.request_id, participant)\n                                        .then(function(data){\n                                            winston.verbose(\"Chat21 conversation archived result \"+ JSON.stringify(data));\n                                    \n                                            chat21Event.emit('conversation.archived', data);                                               \n\n                                }).catch(function(err) {\n                                    winston.error(\"Chat21 archived err\", err);\n                                    chat21Event.emit('conversation.archived.error', err);\n                                });\n                       });\n\n                        //    archive: function(recipient_id, user_id){\n                       chat21.conversations.archive(request.request_id, \"system\")\n                       .then(function(data){\n                           winston.verbose(\"Chat21 archived for system\"+ JSON.stringify(data));\n                   \n                           chat21Event.emit('conversation.archived', data);                                               \n\n                        }).catch(function(err) {\n                            winston.error(\"Chat21 archived  for system err\", err);\n                            chat21Event.emit('conversation.archived.error', err);\n                        });\n\n                        \n                        //  request.lead can be undefined because some test case uses the old deprecated method requestService.createWithId.\n                        if (request.lead) {\n                            // lead_id used. Change it?\n                            chat21.conversations.archive(request.request_id, request.lead.lead_id)  //                        chat21.conversations.archive(request.request_id, request.requester_id)<-desnt'archive\n\n                            .then(function(data){\n                                winston.verbose(\"Chat21 archived for request.lead.lead_id\"+ JSON.stringify(data));\n                        \n                                chat21Event.emit('conversation.archived', data);                                               \n     \n                             }).catch(function(err) {\n                                 winston.error(\"Chat21 archived for request.lead.lead_id err\", err);\n                                 chat21Event.emit('conversation.archived.error', err);\n                             });\n                        }\n                        \n                    }\n                });\n            });\n            \n            requestEvent.on('request.delete', function(request) {\n\n                winston.debug(\"chat21Handler requestEvent request.delete called\" , request);\n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                        winston.debug(\"Chat21Sender deleting conversations for \",request.participants);\n\n                        chat21.conversations.delete(request.request_id).then((data) => {\n                            winston.verbose(\"Chat21 conversation archived result \"+ JSON.stringify(data));\n                            chat21Event.emit('conversation.deleted', data);                                               \n                        }).catch((err) => {\n                            winston.error(\"Chat21 deleted err\", err);\n                            chat21Event.emit('conversation.deleted.error', err);\n                        })\n                    }\n                })\n\n            });\n\n             requestEvent.on('request.participants.update',  function(data) {      \n                 \n                winston.debug(\"chat21Handler requestEvent request.participants.update called\" , data);\n\n                   let request = data.request;\n                   let removedParticipants = data.removedParticipants;\n                \n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                        \n\n                     \n                        let requestObj = request.toJSON();\n                        \n                        winston.verbose(\"setting chat21 group for request.participants.update for request with id: \" + requestObj._id);\n                    \n                        var groupId = request.request_id;\n\n                        let members = [];\n                        \n                        members.push(\"system\");\n\n                        // qui errore participants sembra 0,1 object ???\n                        request.participants.forEach(function(participant,index) {\n                            members.push(participant);\n                        });\n                        // requestObj.participants;\n                        // var members = reqParticipantArray;\n\n                        if (request.lead) {\n                            // lead_id used. Change it?\n                            members.push(request.lead.lead_id);\n                        }\n                        winston.verbose(\"Chat21 group with members for request.participants.update: \" , members);  \n\n                         //setMembers: function(members, group_id){\n                                //racecondition  \n                        chat21.groups.setMembers(members, groupId).then(function(data) {\n                                winston.verbose(\"Chat21 group set for request.participants.update : \" + JSON.stringify(data));      \n                                chat21Event.emit('group.setMembers', data);                                          \n                            }).catch(function(err) {\n                                winston.error(\"Error setting chat21 group for request.participants.update \", err);\n                                chat21Event.emit('group.setMembers.error', err);\n                            });\n\n\n                        // let oldParticipants = data.beforeRequest.participants;\n                        // winston.info(\"oldParticipants \", oldParticipants);\n\n                        // let newParticipants = data.request.participants;\n                        // winston.info(\"newParticipants \", newParticipants);\n\n                        // var removedParticipants = oldParticipants.filter(d => !newParticipants.includes(d));\n                        // winston.info(\"removedParticipants \", removedParticipants);\n\n                       \n\n                        removedParticipants.forEach(function(removedParticipant) {\n                            winston.debug(\"removedParticipant \", removedParticipant);\n\n                            // archive: function(recipient_id, user_id){\n                            //racecondition?low it's not dangerous archive a conversation that doen't exist\n\n                            chat21.conversations.archive(request.request_id, removedParticipant)\n                            .then(function(data){\n                                winston.verbose(\"Chat21 archived \"+ JSON.stringify(data));\n                        \n                                chat21Event.emit('conversation.archived', data);                                               \n        \n                                }).catch(function(err) {\n                                    winston.error(\"Chat21 archived err\", err);\n                                    chat21Event.emit('conversation.archived.error', err);\n                                });\n\n                        });\n                        \n\n\n\n                    }\n                });\n            });\n            \n            \n               requestEvent.on('request.participants.join',  function(data) {       \n                   winston.debug(\"chat21Handler requestEvent request.participants.join called\" , data);\n\n                   let request = data.request;\n                   let member = data.member;\n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                        \n\n                     \n                        // let requestObj = request.toJSON();\n                        \n                        var groupId = request.request_id;\n\n                        winston.verbose(\"joining member \" + member +\" for chat21 group with request : \" + groupId);\n                                            \n                             //racecondition?\n\n                         //join: function(member_id, group_id){\n                        chat21.groups.join(member, groupId).then(function(data) {\n                                winston.verbose(\"Chat21 group joined: \" + JSON.stringify(data));      \n                                chat21Event.emit('group.join', data);                                          \n                            }).catch(function(err) {\n                                winston.error(\"Error joining chat21 group \", err);\n                                chat21Event.emit('group.join.error', err);\n                            });\n\n\n\n                    }\n                });\n            });\n            \n            \n               requestEvent.on('request.participants.leave',  function(data) {     \n                   winston.debug(\"chat21Handler requestEvent request.participants.leave called\" , data);\n\n                   let request = data.request;\n                   let member = data.member;\n\n                setImmediate(() => {\n                    if (request.channelOutbound.name === ChannelConstants.CHAT21) {\n\n                        chat21.auth.setAdminToken(adminToken);\n\n                     \n\n                     \n                        // let requestObj = request.toJSON();\n                        \n                        var groupId = request.request_id;\n\n                        winston.verbose(\"leaving \" + member +\" for chat21 group for request with id: \" + groupId);\n                                   \n                        //racecondition?\n                         //leave: function(member_id, group_id){\n                        chat21.groups.leave(member, groupId).then(function(data) {\n                                winston.verbose(\"Chat21 group leaved: \" + JSON.stringify(data));      \n                                chat21Event.emit('group.leave', data);                                          \n                            }).catch(function(err) {\n                                winston.error(\"Error leaving chat21 group \", err);\n                                chat21Event.emit('group.leave.error', err);\n                            });\n\n\n                            // anche devi archiviare la conversazione per utente corrente \n                            //racecondition?\n                            chat21.conversations.archive(request.request_id, member)\n                            .then(function(data){\n                                winston.verbose(\"Chat21 archived \" + JSON.stringify(data));\n                        \n                                chat21Event.emit('conversation.archived', data);                                               \n     \n                             }).catch(function(err) {\n                                 winston.error(\"Chat21 archived err\", err);\n                                 chat21Event.emit('conversation.archived.error', err);\n                             });\n\n                           \n\n\n                    }\n                });\n            })\n            \n\n\n\n\n            groupEvent.on('group.create',  function(group) {                       \n\n                if (process.env.SYNC_CHAT21_GROUPS !==\"true\") {\n                    winston.debug(\"Sync Tiledesk to Chat21 groups disabled\");\n                    return;\n                }\n\n                winston.verbose(\"Creating chat21 group\", group);\n                \n                setImmediate(() => {\n\n                    chat21.auth.setAdminToken(adminToken);\n\n\n                    var groupMembers = group.members;\n                    winston.debug(\"groupMembers \", groupMembers); \n                    \n                    var group_id = \"group-\" + group._id;\n                    winston.debug(\"group_id :\" + group_id); \n\n                    return chat21.groups.create(group.name, groupMembers, undefined, group_id).then(function(data) {\n                        winston.verbose(\"Chat21 group created: \" + JSON.stringify(data));      \n                        // TODO ritorna success anche se \n                        // verbose: Chat21 group created: {\"success\":false,\"err\":{\"message\":\"Channel closed\",\"stack\":\"IllegalOperationError: Channel closed\\n    at ConfirmChannel.<anonymous> (/usr/src/app/node_modules/amqplib/lib/channel.js:160:11)\\n    at ConfirmChannel.Channel.publish (/usr/src/app/node_modules/amqplib/lib/callback_model.js:171:17)\\n    at ConfirmChannel.publish (/usr/src/app/node_modules/amqplib/lib/callback_model.js:301:36)\\n    at Chat21Api.publish (/usr/src/app/chat21Api/index.js:1028:29)\\n    at Chat21Api.sendMessageRaw (/usr/src/app/chat21Api/index.js:762:14)\\n    at Chat21Api.sendGroupWelcomeMessage (/usr/src/app/chat21Api/index.js:205:14)\\n    at /usr/src/app/chat21Api/index.js:99:22\\n    at /usr/src/app/chat21Api/index.js:234:17\\n    at /usr/src/app/chatdb/index.js:77:9\\n    at executeCallback (/usr/src/app/node_modules/mongodb/lib/operations/execute_operation.js:70:5)\\n    at updateCallback (/usr/src/app/node_modules/mongodb/lib/operations/update_one.js:41:3)\\n    at /usr/src/app/node_modules/mongodb/lib/operations/update_one.js:24:64\\n    at handleCallback (/usr/src/app/node_modules/mongodb/lib/utils.js:128:55)\\n    at /usr/src/app/node_modules/mongodb/lib/operations/common_functions.js:378:5\\n    at handler (/usr/src/app/node_modules/mongodb/lib/core/sdam/topology.js:913:24)\",\"stackAtStateChange\":\"Stack capture: Socket error\\n    at Connection.C.onSocketError (/usr/src/app/node_modules/amqplib/lib/connection.js:354:13)\\n    at Connection.emit (events.js:314:20)\\n    at Socket.go (/usr/src/app/node_modules/amqplib/lib/connection.js:481:12)\\n    at Socket.emit (events.js:314:20)\\n    at emitReadable_ (_stream_readable.js:557:12)\\n    at processTicksAndRejections (internal/process/task_queues.js:83:21)\"}}\n                        chat21Event.emit('group.create', data);                                          \n                    }).catch(function(err) {\n                        winston.error(\"Error creating chat21 group \", err);\n                        chat21Event.emit('group.create.error', err);\n                    });\n\n                });\n\n             });\n\n\n             groupEvent.on('group.update',  function(group) {                       \n\n                if (process.env.SYNC_CHAT21_GROUPS !==\"true\") {\n                    winston.debug(\"Sync Tiledesk to Chat21 groups disabled\");\n                    return;\n                }\n\n                winston.verbose(\"Updating chat21 group\", group);\n                \n                setImmediate(() => {\n\n                    chat21.auth.setAdminToken(adminToken);\n\n\n                    var groupMembers = group.members;\n                    winston.debug(\"groupMembers \", groupMembers); \n                    \n                    var group_id = \"group-\" + group._id;\n                    winston.debug(\"group_id :\" + group_id); \n\n                            // update: function(name, owner, attributes, group_id){\n                    return chat21.groups.update(group.name, undefined, undefined, group_id).then(function(data) {\n                        winston.verbose(\"Chat21 group.update updated: \" + JSON.stringify(data));      \n                        chat21Event.emit('group.update', data);     \n                        return chat21.groups.setMembers(groupMembers, group_id).then(function(data) {      \n                            winston.verbose(\"Chat21 group.update set: \" + JSON.stringify(data));      \n                            chat21Event.emit('group.setMembers', data);          \n                        }).catch(function(err) {\n                            winston.error(\"Error setMembers chat21 group.update \", err);\n                            chat21Event.emit('group.setMembers.error', err);\n                        });                             \n                    }).catch(function(err) {\n                        winston.error(\"Error setting chat21 group.update \", err);\n                        chat21Event.emit('group.update.error', err);\n                    });\n\n                });\n\n             });\n\n\n\n\n\n             groupEvent.on('group.delete',  function(group) {                       \n\n                if (process.env.SYNC_CHAT21_GROUPS !==\"true\") {\n                    winston.debug(\"Sync Tiledesk to Chat21 groups disabled\");\n                    return;\n                }\n\n                winston.verbose(\"Deleting chat21 group for group.delete\", group);\n                \n                setImmediate(() => {\n\n                    chat21.auth.setAdminToken(adminToken);\n                  \n                    var group_id = \"group-\" + group._id;\n                    winston.debug(\"group_id :\" + group_id); \n\n                    //Remove members but group remains.\n\n                    return chat21.groups.setMembers([\"system\"], group_id).then(function(data) {      \n                        winston.verbose(\"Chat21 group set for group.delete : \" + JSON.stringify(data));      \n                        chat21Event.emit('group.setMembers', data);          \n                    }).catch(function(err) {\n                        winston.error(\"Error setMembers chat21 group for group.delete\", err);\n                        chat21Event.emit('group.setMembers.error', err);\n                    });           \n\n                });\n\n             });\n\n    }\n\n    \n}\n\nvar chat21Handler = new Chat21Handler();\nmodule.exports = chat21Handler;\n"
  },
  {
    "path": "channels/chat21/chat21Util.js",
    "content": "\n\n\n// ***** UNUSED\nclass Chat21Util {\n    \n    getButtonFromText(message, bot, qna) { \n            var that = this;\n            var text = message.text;\n            return new Promise(function(resolve, reject) {\n    \n                var repl_message = Object.assign({}, message);\n               \n                // cerca i bottoni eventualmente definiti\n                var button_pattern = /^\\*.*/mg; // buttons are defined as a line starting with an asterisk\n                var text_buttons = text.match(button_pattern);\n\n\n                var image_pattern = /^\\\\image:.*/mg; \n                var imagetext = text.match(image_pattern);\n\n                var webhook_pattern = /^\\\\webhook:.*/mg; \n                var webhooktext = text.match(webhook_pattern);\n\n\n                if (text_buttons) {\n                    var text_with_removed_buttons = text.replace(button_pattern,\"\").trim();\n                    repl_message.text = text_with_removed_buttons\n                    var buttons = []\n                    text_buttons.forEach(element => {\n                    // console.log(\"button \", element)\n                    var remove_extra_from_button = /^\\*/mg;\n                    var button_text = element.replace(remove_extra_from_button, \"\").trim()\n                    var button = {}\n                    button[\"type\"] = \"text\"\n                    button[\"value\"] = button_text\n                    buttons.push(button)\n                    });\n                    repl_message.attributes =\n                    { \n                    attachment: {\n                        type:\"template\",\n                        buttons: buttons\n                    }\n                    }\n                    repl_message.type = \"text\";            \n    \n                \n                }  else if (imagetext && imagetext.length>0) {\n                    var imageurl = imagetext[0].replace(\"\\\\image:\",\"\").trim();\n                    // console.log(\"imageurl \", imageurl)\n                    var text_with_removed_image = text.replace(image_pattern,\"\").trim();\n                    repl_message.text = text_with_removed_image + \" \" + imageurl\n                    repl_message.metadata = {src: imageurl, width:200, height:200};\n                    repl_message.type = \"image\";\n                }\n    \n    \n               \n                else if (webhooktext && webhooktext.length>0) {\n                    var webhookurl = webhooktext[0].replace(\"\\\\webhook:\",\"\").trim();\n                    // console.log(\"webhookurl \", webhookurl)\n    \n                    return request({                        \n                        uri :  webhookurl,\n                        headers: {\n                            'Content-Type': 'application/json'\n                        },\n                        method: 'POST',\n                        json: true,\n                        body: {text: text, bot: bot, message: message, qna: qna},\n                        }).then(response => {\n                            if (response.statusCode >= 400) {                  \n                                return reject(`HTTP Error: ${response.statusCode}`);\n                            }\n                            // console.log(\"webhookurl repl_message \", response);\n                            that.getButtonFromText(response.text,message, bot,qna).then(function(bot_answer) {\n                                return resolve(bot_answer);\n                            });\n                        });\n                 \n                }else {\n                    // console.log(\"repl_message \", repl_message)\n                    return resolve(repl_message);\n                }\n    \n    \n               \n            });\n        }\n\n\n// move to messageAction  no create a new module messageInterpreter??\n\n\n\n\n        findSplits(result) {\n            var commands = []\n            const text = result['fulfillmentText'] // \"parte 1\\\\splittesto12\\\\split\\npt2.capone detto\\\\split:4000\\npt.3. muggio\\\\split\\npt. 4.Andtonino Mustacchio\"\n            // const text = \"parte 1NO\\\\splittesto12\\\\split\\npt2.capone detto\\\\split:4000\\npt.3. muggio\\\\split\\npt. 4.Dammi la tua email\"\n            const split_pattern = /^(\\\\split[:0-9]*)/mg //ex. \\split:500\n            var parts = text.split(split_pattern)\n            for (var i=0; i < parts.length; i++) {\n                let p = parts[i]\n                console.log(\"part: \" + p)\n                if (i % 2 != 0) {\n                // split command\n                console.log(\"split command: \" + p)\n                var split_parts = p.split(\":\")\n                var wait_time = 1000\n                if (split_parts.length == 2) {\n                    wait_time = split_parts[1]\n                }\n                console.log(\"wait time: \" + wait_time)\n                var command = {}\n                command.type = \"wait\"\n                command.time = parseInt(wait_time, 10)\n                commands.push(command)\n                }\n                else {\n                // message command\n                var command = {}\n                command.type = \"message\"\n                command.text = p.trim()\n                commands.push(command)\n                if ( i == parts.length -1 &&\n                    result['fulfillmentMessages'] &&\n                    result['fulfillmentMessages'][1] &&\n                    result['fulfillmentMessages'][1].payload) {\n                    command.payload = result['fulfillmentMessages'][1].payload\n                }\n                }\n            }\n            commands.forEach(c => {\n                console.log(\"* * * * * * * * * command: \", c)\n            })\n            return commands\n        }\n    \n        parseReply(text) {\n            \n    \n            let TEXT_KEY = 'text'\n    \n            let TYPE_KEY = 'type'\n            let ATTRIBUTES_KEY = 'attributes'\n            let METADATA_KEY = \"metadata\"\n            let TYPE_IMAGE = 'image'\n    \n            var reply = {}\n          \n            console.log(\"TEXT: \", text)\n            reply[TEXT_KEY] = text\n            reply[ATTRIBUTES_KEY] = null\n          \n            // looks for images\n            var image_pattern = /^\\\\image:.*/mg; // images are defined as a line starting with \\image:IMAGE_URL\n            // console.log(\"Searching images with image_pattern: \", image_pattern)\n            var images = text.match(image_pattern);\n            // console.log(\"images: \", images)\n            if (images && images.length > 0) {\n              const image_text = images[0]\n              var text = text.replace(image_text,\"\").trim()\n              const image_url = image_text.replace(\"\\\\image:\", \"\")\n              reply[TEXT_KEY] = text\n              reply[TYPE_KEY] = TYPE_IMAGE\n              reply[METADATA_KEY] = {\n                src: image_url,\n                width: 200,\n                height: 200\n              }\n            }\n          \n            // looks for bullet buttons\n            var button_pattern = /^\\*.*/mg; // button pattern is a line that starts with *TEXT_OF_BUTTON (every button on a line)\n            var text_buttons = text.match(button_pattern);\n            if (text_buttons) {\n              // ricava il testo rimuovendo i bottoni\n              var text_with_removed_buttons = text.replace(button_pattern,\"\").trim();\n              reply[TEXT_KEY] = text_with_removed_buttons\n              // estrae i bottoni\n              var buttons = []\n              text_buttons.forEach(element => {\n                var remove_extra_from_button = /^\\*/mg; // removes initial \"*\"\n                var button_text = element.replace(remove_extra_from_button, \"\").trim()\n                var button = {}\n                button[TYPE_KEY] = \"text\"\n                button[\"value\"] = button_text\n                buttons.push(button)\n                console.log(\"Added button: \" + button_text)\n              });\n              if (reply[ATTRIBUTES_KEY] == null) {\n                reply[ATTRIBUTES_KEY] = {}\n              }\n              reply[ATTRIBUTES_KEY][\"attachment\"] = {\n                type:\"template\",\n                buttons: buttons\n              }\n            }\n            return reply\n        }\n\n\n        \n}\n\nvar chat21Util = new Chat21Util();\nmodule.exports = chat21Util;"
  },
  {
    "path": "channels/chat21/chat21WebHook.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Request = require(\"../../models/request\");\nvar requestService = require('../../services/requestService');\nvar messageService = require('../../services/messageService');\nvar leadService = require('../../services/leadService');\nvar eventService = require('../../pubmodules/events/eventService');\nvar Project_user = require(\"../../models/project_user\");\nvar RequestConstants = require(\"../../models/requestConstants\");\n\nvar cacheUtil = require('../../utils/cacheUtil');\nvar cacheEnabler = require(\"../../services/cacheEnabler\");\n\n\nvar mongoose = require('mongoose');\nvar winston = require('../../config/winston');\nvar MessageConstants = require(\"../../models/messageConstants\");\nvar ProjectUserUtil = require(\"../../utils/project_userUtil\");\nvar RequestUtil = require(\"../../utils/requestUtil\");\nconst authEvent = require('../../event/authEvent');\n\nvar syncJoinAndLeaveGroupEvent =  false;\nif (process.env.SYNC_JOIN_LEAVE_GROUP_EVENT === true || process.env.SYNC_JOIN_LEAVE_GROUP_EVENT ===\"true\") {\n  syncJoinAndLeaveGroupEvent = true;\n}\nwinston.info(\"Chat21 Sync JoinAndLeave Support Group Event: \" + syncJoinAndLeaveGroupEvent);\n\nvar allowReopenChat =  false;   //It's work only with firebase chat engine\nif (process.env.ALLOW_REOPEN_CHAT === true || process.env.ALLOW_REOPEN_CHAT ===\"true\") {\n  allowReopenChat = true;\n}\nwinston.info(\"Chat21 allow reopen chat: \" + allowReopenChat);\n\n\nrouter.post('/', function (req, res) {\n\n\n  winston.debug(\"req.body.event_type: \" + req.body.event_type);\n\n                                                    //Deprecated\n  if (req.body.event_type == \"message-sent\" || req.body.event_type == \"new-message\") {\n    //with projectid\n    // curl -X POST -H 'Content-Type:application/json'  -d '{\"event_type\": \"new-message\", \"data\":{\"sender\":\"sender\", \"sender_fullname\": \"sender_fullname\", \"recipient\":\"123456789123456789\", \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\", \"projectid\":\"987654321\"}}' http://localhost:3000/chat21/requests\n    //with recipient with existing projectid\n    // curl -X POST -H 'Content-Type:application/json'  -d '{\"event_type\": \"new-message\", \"data\":{\"sender\":\"sender\", \"sender_fullname\": \"sender_fullname\", \"recipient\":\"123456789123456789\", \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\"}}' http://localhost:3000/chat21/requests\n\n    //with recipient with no projectid\n    // curl -X POST -H 'Content-Type:application/json'  -d '{\"event_type\": \"new-message\", \"data\":{\"sender\":\"sender\", \"sender_fullname\": \"sender_fullname\", \"recipient\":\"1234567891234567891\", \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\"}}' http://localhost:3000/chat21/requests\n\n\n    winston.debug(\"event_type\", \"new-message\");\n\n    var message = req.body.data;\n    \n    winston.debug(\"message text: \" + message.text);\n\n\n    // before request_id id_project unique commented\n    /*\n    var projectid;\n    if (message.attributes) {            \n      projectid = message.attributes.projectId;\n      winston.debug(\"chat21 projectid\", projectid);\n    }\n\n    if (!projectid) {\n      winston.warn(\"projectid is null. Not a support message\");\n      return res.status(400).send({success: false, msg: 'projectid is null. Not a support message'});\n    }\n    */\n    // before request_id id_project unique commented\n\n\n\n    winston.debug(\"Chat21 message\", message);\n\n        // requestcachefarequi nocachepopulatereqired        \n        let q = Request.findOne({request_id: message.recipient}) \n\n        if (cacheEnabler.request) {\n          q.cache(cacheUtil.defaultTTL, \"requests:request_id:\"+message.recipient+\":simple\"); //request_cache\n                                                                                            // project_id not available //without project for chat21 webhook\n          winston.debug('request cache enabled');\n        }\n        return q.exec(function(err, request) {\n\n        // before request_id id_project unique - commented\n               \n\n          if (err) {\n            return res.status(500).send({success: false, msg: 'Error getting the request.', err:err});\n          }\n\n          winston.debug('request cache simple 1', request);\n\n          if (!request) { //the request doen't exists create it\n\n            winston.debug(\"request not exists with request_id: \" + message.recipient);\n\n            var departmentid = \"default\";\n\n\n            var language = message.language;\n            winston.debug(\"chat21 language\", language);\n\n            var sourcePage;\n            var client;\n            var userEmail;\n            var userFullname;\n            var projectid;  // before request_id id_project unique - commented\n\n            var requestStatus = undefined;\n\n            if (message.attributes) {\n\n              // before request_id id_project unique - commented\n              projectid = message.attributes.projectId;\n              winston.debug(\"chat21 projectid\", projectid);\n\n              departmentid = message.attributes.departmentId;\n              winston.debug(\"chat21 departmentid\", departmentid);\n\n              sourcePage = message.attributes.sourcePage;\n              winston.debug(\"chat21 sourcePage\", sourcePage);\n\n              client = message.attributes.client;\n              winston.debug(\"chat21 client\", client);\n\n\n\n              userEmail = message.attributes.userEmail;\n              winston.debug(\"chat21 userEmail\", userEmail);\n\n              userFullname = message.attributes.userFullname;\n              winston.debug(\"chat21 userFullname\", userFullname);\n\n              // TODO proactive status\n              // if (message.attributes.subtype === \"info\") {                    \n              //   requestStatus = 50;\n              // }\n            }\n\n            winston.debug(\"requestStatus \" + requestStatus);\n\n            // before request_id id_project unique - commented\n            if (!projectid) {\n              winston.verbose(\"projectid is null. Not a support message\");\n              return res.status(400).send({ success: false, msg: 'projectid is null. Not a support message' });\n            }\n\n\n            if (!message.recipient.startsWith(\"support-group\")) {\n              winston.verbose(\"recipient not starts wiht support-group. Not a support message\");\n              return res.status(400).send({ success: false, msg: \"recipient not starts wiht support-group. Not a support message\" });\n            }\n\n\n            if (!userFullname) {\n              userFullname = message.sender_fullname;\n            }\n\n\n\n\n            var leadAttributes = message.attributes;\n            leadAttributes[\"senderAuthInfo\"] = message.senderAuthInfo;\n\n            // winston.debug(\"userEmail is defined\");\n            // createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy)\n            return leadService.createIfNotExistsWithLeadId(message.sender, userFullname, userEmail, projectid, null, leadAttributes)\n              .then(function (createdLead) {\n\n                var rAttributes = message.attributes;\n                rAttributes[\"senderAuthInfo\"] = message.senderAuthInfo;\n                winston.debug(\"rAttributes\", rAttributes);\n\n\n\n\n                // message.sender is the project_user id created with firebase custom auth\n\n                var isObjectId = mongoose.Types.ObjectId.isValid(message.sender);\n                winston.debug(\"isObjectId:\" + isObjectId);\n\n                var queryProjectUser = { id_project: projectid, status: \"active\" };\n\n                if (isObjectId) {\n                  queryProjectUser.id_user = message.sender;\n                } else {\n                  queryProjectUser.uuid_user = message.sender;\n                }\n                winston.debug(\"queryProjectUser\", queryProjectUser);\n\n\n                return Project_user.findOne(queryProjectUser)\n                  // .cache(cacheUtil.defaultTTL, projectid+\":project_users:request_id:\"+requestid)\n                  .exec(function (err, project_user) {\n\n                    var project_user_id = null;\n\n                    if (err) {\n                      winston.error(\"Error getting the project_user_id\", err);\n                    }\n\n                    if (project_user) {\n                      winston.debug(\"project_user\", project_user);\n                      project_user_id = project_user.id;\n                      winston.debug(\"project_user_id: \" + project_user_id);\n                    } else {\n                      // error->utente bloccato oppure non autenticator request.requester sarà nulll...⁄\n                      return winston.error(\"project_user not found with query: \", queryProjectUser);\n                    }\n\n\n                    // var auto_close;\n\n                    // // qui projecy nn c'è devi leggerlo\n                    // // if (req.project.attributes.auto_close === false) {\n                    // //   auto_close = 10;\n                    // // }\n\n\n                    var new_request = {\n                      request_id: message.recipient, project_user_id: project_user_id, lead_id: createdLead._id, id_project: projectid, first_text: message.text,\n                      departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: client, status: requestStatus, createdBy: undefined,\n                      attributes: rAttributes, subject: undefined, preflight: false, channel: undefined, location: undefined,\n                      lead: createdLead, requester: project_user\n                      // , auto_close: auto_close\n                    };\n\n                    winston.debug(\"new_request\", new_request);\n\n                    return requestService.create(new_request).then(function (savedRequest) {\n\n\n                      //return requestService.createWithIdAndRequester(message.recipient, project_user_id, createdLead._id, projectid, message.text, \n                      // departmentid, sourcePage, language, client, requestStatus, null, rAttributes).then(function (savedRequest) {\n\n\n                      var messageId = undefined;\n                      if (message.attributes && message.attributes.tiledesk_message_id) {\n                        messageId = message.attributes.tiledesk_message_id;\n                      }\n\n                      // upsert(id, sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language)\n                      return messageService.upsert(messageId, message.sender, message.sender_fullname, message.recipient, message.text,\n                        projectid, null, MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED, message.attributes, message.type, message.metadata, language).then(function (savedMessage) {\n                          return res.json(savedRequest);\n                          // return requestService.incrementMessagesCountByRequestId(savedRequest.request_id, savedRequest.id_project).then(function(savedRequestWithIncrement) {\n                          // return res.json(savedRequestWithIncrement);\n                          // });\n\n\n                        }).catch(function (err) {\n                          winston.error('Error creating the request object.', err);\n                          return res.status(500).send({ success: false, msg: 'Error creating the request object.', err: err });\n                        });\n                    }).catch((err) => {\n                      winston.error('(Chat21Webhook) Error creating the request object ', err);\n                      return res.status(500).send({ success: false, msg: 'Error creating the request object.', err: err });\n                    });\n                  });\n\n              });\n\n\n\n          } else {\n\n        \n\n            winston.debug(\"request  exists\", request.toObject());\n\n            // var projectid;\n            // if (message.attributes) {\n        \n            //   projectid = message.attributes.projectId;\n            //   winston.debug(\"chat21 projectid\", projectid);\n            // }\n        \n            // if (!projectid) {\n            //   winston.debug(\"projectid is null. Not a support message\");\n            //   return res.status(400).send({success: false, msg: 'projectid is null. Not a support message'});\n            // }\n            \n            if (!message.recipient.startsWith(\"support-group\")) {\n              winston.debug(\"recipient not starts with support-group. Not a support message\");\n              return res.status(400).send({success: false, msg: \"recipient not starts with support-group. Not a support message\"});\n            }\n        \n            var messageId = undefined;\n            var language = undefined;\n            if (message.attributes) {\n              if (message.attributes.tiledesk_message_id) {\n                messageId = message.attributes.tiledesk_message_id;\n              }\n              if (message.attributes.language) {\n                language = message.attributes.language;\n              }\n            }\n\n\n            // upsert(id, sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n            return messageService.upsert(messageId, message.sender, message.sender_fullname, message.recipient, message.text,\n              request.id_project, null, MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED, message.attributes, message.type, message.metadata, language).then(function(savedMessage){      \n\n                // TODO se stato = 50 e scrive visitatotre sposto a stato 100 poi queuue lo smista\n\n                // TOOD update also request attributes and sourcePage\n                if (message.sender !== 'system') {\n                  Request.findOneAndUpdate({request_id: request.request_id, id_project: request.id_project}, { \"attributes.last_message\": savedMessage}).catch((err) => {\n                    winston.error(\"Create message - saving last message in request error: \", err);\n                  })\n                }\n\n                    // return requestService.incrementMessagesCountByRequestId(request.request_id, request.id_project).then(function(savedRequest) {\n                      // winston.debug(\"savedRequest.participants.indexOf(message.sender)\", savedRequest.participants.indexOf(message.sender));\n                      winston.debug(\"before updateWaitingTimeByRequestId*******\",request.participants, message.sender);\n                      winston.debug(\"updateWaitingTimeByRequestId******* message: \"+ message.sender);\n                      // TODO it doesn't work for internal requests bacause participanets == message.sender⁄\n                      if (request.participants && request.participants.indexOf(message.sender) > -1) { //update waiitng time if write an  agent (member of participants)\n                        winston.debug(\"updateWaitingTimeByRequestId*******\");\n                                                                                                                  //leave this parameter to true because it is used by websocket to notify request.update                                 \n                        return requestService.updateWaitingTimeByRequestId(request.request_id, request.id_project, true).then(function(upRequest) {\n                          return res.json(upRequest);\n                        });\n                      }else {\n                        return res.json(savedMessage);\n                      }\n                    // });\n              }).catch(function(err){ \n                winston.error(\"Error creating chat21 webhook message: \"+ JSON.stringify({err: err, message: message}));\n                return res.status(500).send({success: false, msg: 'Error creating message', err:err });\n              });\n\n\n\n          }\n        \n\n\n        });\n\n \n   \n      \n      // curl -X POST -H 'Content-Type:application/json'  -d '{ \"event_type\": \"deleted-conversation\", \"createdAt\": 1537973334802, \"app_id\": \"tilechat\", \"user_id\": \"system\", \"recipient_id\": \"support-group-LNPQ57JnotOEEwDXr9b\"}' http://localhost:3000/chat21/requests\n                                                                                         // depreated\n// this is a deprecated method for closing request. In the past used by chat21 cloud function support api /close method called by old versions of ionic. The new version of the ionic (both for firebase and mqtt) chat call Tiledesk DELETE /requests/:req_id endpoint so  this will not be used                                                                                        \n    } else if (req.body.event_type == \"conversation-archived\" || req.body.event_type == \"deleted-conversation\" ) {\n      winston.debug(\"event_type deleted-conversation\");\n\n      var conversation = req.body.data;\n      winston.debug(\"conversation\",conversation);\n\n      var user_id = req.body.user_id;\n      winston.debug(\"user_id: \"+user_id);\n\n      var recipient_id = req.body.convers_with;\n\n      if (!recipient_id && req.body.recipient_id) { //back compatibility\n        recipient_id = req.body.recipient_id;\n      }\n      winston.debug(\"recipient_id: \"+recipient_id);\n\n      \n\n \n      if (!recipient_id.startsWith(\"support-group\")){\n        winston.debug(\"not a support conversation\");\n        return res.status(400).send({success: false, msg: \"not a support conversation\" });\n      }\n\n     \n\n      if (user_id!=\"system\"){\n        winston.debug(\"we close request only for system conversation\");\n        return res.status(400).send({success: false, msg: \"not a system conversation\" });\n      }\n\n// chiudi apri e chiudi. projectid nn c'è in attributes\n\n                // prendi id progetto dal recipient_id e nn da attributes. facciamo release intermedia nel cloud con solo questa modifica\n\n              var projectId = RequestUtil.getProjectIdFromRequestId(recipient_id);\n\n              var isObjectId = mongoose.Types.ObjectId.isValid(projectId);\n              winston.debug(\"isObjectId:\"+ isObjectId);\n\n              winston.debug(\"attributes\",conversation.attributes);\n\n              if (!projectId || !isObjectId) { //back compatibility when projectId were always presents in the attributes (firebase)                \n                projectId = conversation.attributes.projectId;\n                winston.verbose('getting projectId from attributes (back compatibility): '+ projectId);\n              }\n                \n              winston.debug('projectId: '+ projectId);\n\n              if (!projectId) {\n                return res.status(500).send({success: false, msg: \"Error projectid is not presents in attributes \" });\n              }\n              \n              var query = {request_id: recipient_id, id_project: projectId};\n              winston.debug('query:'+ projectId);\n              \n              winston.debug('conversation-archived Request.findOne(query);:');\n              let q = Request.findOne(query);\n              // if (cacheEnabler.request) {\n              //   q.cache(cacheUtil.defaultTTL, projectId+\":requests:request_id:\"+recipient_id+\":simple\"); //request_cache NOT IMPORTANT HERE\n              //   winston.debug('project cache enabled');\n              // }\n              return q.exec(function(err, request) {\n\n                if (err) {\n                  winston.error(\"Error finding request with query \", query);\n                  return res.status(500).send({success: false, msg: \"Error finding request with query \" + query, err:err });\n                }\n                if (!request) {\n                  winston.warn(\"request not found for query \", query);\n                  return res.status(404).send({success: false, msg: \"Request with query \" + JSON.stringify(query) + \" not found\" });\n                }\n              \n                if (request.status === RequestConstants.CLOSED) {\n                   winston.verbose(\"request already closed with id: \" + recipient_id);\n                  return res.send({success: false, msg: \"request already closed with id: \" + recipient_id});\n                }\n\n\n                    // se agente archivia conversazione allora chiude anche richiesta\n                    // return requestService.setParticipantsByRequestId(recipient_id, firestoreProjectid, firestoreMembersAsArray).then(function(updatedParticipantsRequest) {\n                      // winston.debug('updatedParticipantsRequest', updatedParticipantsRequest);\n                      // manca id\n\n                      // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by)\n                      const closed_by = user_id;\n                      return requestService.closeRequestByRequestId(recipient_id, projectId, false, true,closed_by ).then(function(updatedStatusRequest) {\n                        \n                        winston.debug('updatedStatusRequest', updatedStatusRequest.toObject());\n                        return res.json(updatedStatusRequest);                      \n                    }).catch(function(err){\n                      winston.error(\"Error closing request\", err);\n                      return res.status(500).send({success: false, msg: 'Error closing request', err:err });\n                    });\n\n\n\n                  // });\n            });\n\n\n          \n\n\n      \n\n    }else if (req.body.event_type == \"join-member\") {\n      winston.debug(\"event_type\",\"join-member\");\n\n      winston.debug(\"req.body\", JSON.stringify(req.body));\n\n      if (!syncJoinAndLeaveGroupEvent)  {\n        winston.debug(\"syncJoinAndLeaveGroupEvent is disabled\");\n        return res.status(200).send({success: true, msg: \"syncJoinAndLeaveGroupEvent is disabled\" });\n      }\n\n      var data = req.body.data;\n      //winston.debug(\"data\",data);\n\n      var group = data.group;\n      // winston.debug(\"group\",group);\n\n      var new_member = req.body.member_id;\n      winston.debug(\"new_member: \" + new_member);\n\n      if (new_member==\"system\") {\n        winston.verbose(\"new_member \"+ new_member+ \" not added to participants\");\n        return res.status(400).send({success: false, msg: \"new_member \"+ new_member+ \" not added to participants\" });\n      }\n\n      var request_id = req.body.group_id;\n      winston.debug(\"request_id: \" + request_id);\n\n      var id_project;\n      if (group && group.attributes) {\n        id_project = group.attributes.projectId;\n      }else {\n        winston.verbose(\"id_project \"+ id_project+ \" isn't a support joining\");\n        return res.status(400).send({success: false, msg: \"not a support joining\" });\n      }\n      winston.debug(\"id_project: \" + id_project);\n      \n      // requestcachefarequi populaterequired\n      winston.debug('join-member Request.findOne(query);:');\n      return Request.findOne({request_id: request_id, id_project: id_project})\n          .populate('lead') //TODO posso prenderlo da snapshot senza populate cache_attention\n          .exec(function(err, request) {\n        if (err){\n          winston.error(err);\n           return res.status(500).send({success: false, msg: 'Error joining memeber', err:err });\n        }\n        if (!request) {\n          return res.status(404).send({success: false, msg: 'Request not found for request_id '+ request_id + ' and id_project '+ id_project});\n        }\n\n\n          winston.debug(\"request\",request.toObject());\n                   // lead_id used. Change it?\n\n          // winston.info(\"request.snapshot.lead\",request.snapshot.lead);\n          // if (request.snapshot.lead && request.snapshot.lead.lead_id==new_member) {   \n\n          if (request.lead && request.lead.lead_id==new_member) {            \n            winston.debug(\"don't  joining request.lead or a lead\");\n            return res.status(400).send({success: false, msg: \"don't  joining request.lead or a lead\" });\n          }else {\n\n            // se gia in participants scarta\n            return requestService.addParticipantByRequestId(request_id, id_project, new_member).then(function(updatedRequest) {\n              winston.debug(\"Join memeber ok\");\n              return res.json(updatedRequest);\n            }).catch(function(err){\n              winston.error(\"Error joining memeber\", err);\n              return res.status(500).send({success: false, msg: 'Error joining memeber', err:err });\n            });\n          }\n\n       \n    });\n\n\n  }else if (req.body.event_type == \"leave-member\") {\n    winston.debug(\"event_type\",\"leave-member\");\n    \n    winston.debug(\"req.body\", JSON.stringify(req.body));\n\n    if (!syncJoinAndLeaveGroupEvent)  {\n      winston.debug(\"syncJoinAndLeaveGroupEvent is disabled\");\n      return res.status(200).send({success: true, msg: \"syncJoinAndLeaveGroupEvent is disabled\" });\n    }\n\n\n\n    var data = req.body.data;\n    // winston.debug(\"data\",data);\n\n    var group = data.group;\n    winston.debug(\"group\",group);\n\n    var new_member = req.body.member_id;\n    winston.debug(\"new_member\",new_member);\n\n    var request_id = req.body.group_id;\n    winston.debug(\"request_id\", request_id);\n\n\n    var id_project;\n      if (group && group.attributes) {\n        id_project = group.attributes.projectId;\n      } else {\n        return res.status(400).send({success: false, msg: \"not a support joining\" });\n      }\n      winston.debug(\"id_project\", id_project);\n\n    winston.verbose(\"Chat21WebHook: leaving member : \" + new_member +\" from the request with request_id: \" + request_id +\" from the project with id: \" + id_project);\n\n    return requestService.removeParticipantByRequestId(request_id, id_project, new_member).then(function(updatedRequest) {\n      winston.debug(\"Leave memeber ok\");\n      return res.json(updatedRequest);\n    }).catch(function(err){\n      winston.error(\"Error leaving memeber\", err);\n      return res.status(500).send({success: false, msg: 'Error leaving memeber', err:err });\n    });\n  }\n  \n  else if (req.body.event_type == \"deleted-archivedconversation\" || req.body.event_type == \"conversation-unarchived\") {\n\n    winston.debug(\"event_type\",\"deleted-archivedconversation\");\n\n    winston.debug(\"req.body\",req.body);\n\n    if (!allowReopenChat)  {\n      winston.debug(\"allowReopenChat is disabled\");\n      return res.status(200).send({success: true, msg: \"allowReopenChat is disabled\" });\n    }\n\n\n      var conversation = req.body.data; \n      // winston.debug(\"conversation\",conversation);\n\n      var user_id = req.body.user_id;\n      winston.debug(\"user_id\",user_id);\n\n      var recipient_id = req.body.recipient_id;\n      winston.debug(\"recipient_id\",recipient_id);\n\n     \n//   TODO leggi projectid from support-group\n\n      if (!recipient_id.startsWith(\"support-group\")){\n        winston.debug(\"not a support conversation\");\n        return res.status(400).send({success: false, msg: \"not a support conversation\" });\n      }\n\n\n\n      if (user_id!=\"system\"){\n        winston.debug(\"not a system conversation\");\n        return res.status(400).send({success: false, msg: \"not a system conversation\" });\n      }\n\n\n\n     // scrivo... nuova viene popolato projectid in attributes poi chiudo ed in archived c'è projectid \n      // quando scrivo viene cancellato archived e nuovo messaggio crea conv ma senza project id... lineare che è cosi\n      // si verifica solo se admin (da ionic ) archivia di nuovo senza che widget abbia scritto nulla (widget risetta projectid in properties)\n\n      var id_project;\n      if (conversation && conversation.attributes) {\n        id_project = conversation.attributes.projectId;\n      }else {\n        winston.debug( \"not a support deleting archived conversation\" );\n        return res.status(400).send({success: false, msg: \"not a support deleting archived conversation\" });\n      }\n      winston.debug(\"id_project\", id_project);\n\n\n      return requestService.reopenRequestByRequestId(recipient_id, id_project).then(function(updatedRequest) {\n        return res.json(updatedRequest);\n      }).catch(function(err){\n        winston.error(\"Error reopening request\", err);\n        return res.status(500).send({success: false, msg: 'Error reopening request', err:err });\n      });\n\n\n}\nelse if (req.body.event_type == \"typing-start\") {\n\n  winston.debug(\"event_type typing-start\");\n\n  winston.debug(\"typing-start req.body\",req.body);\n\n\n  var recipient_id = req.body.recipient_id;\n  winston.debug(\"recipient_id\",recipient_id);\n\n  var writer_id = req.body.writer_id;\n  winston.debug(\"writer_id\",writer_id);\n  \n\n  if (writer_id==\"system\") {\n    winston.debug(\"not saving system typings\");\n    return res.status(400).send({success: false, msg: \"not saving system typings\" });\n  }\n  var data = req.body.data;\n  winston.debug(\"data\",data);\n\n  if (!recipient_id.startsWith(\"support-group\")){\n    winston.debug(\"not a support conversation\");\n    return res.status(400).send({success: false, msg: \"not a support conversation\" });\n  }\n\n  \n  // requestcachefarequi nocachepopulatereqired\n  winston.debug('typing-start Request.findOne(query);:');\n  return Request.findOne({request_id: recipient_id})\n                             //TOD  errore cache sistemare e riabbilitare->\n  // .cache(cacheUtil.defaultTTL, req.projectid+\":requests:request_id:\"+recipient_id)   cache_attention\n  .exec(function(err, request) {\n  if (err){\n    winston.error(err);\n    return res.status(500).send({success: false, msg: 'Error finding request', err:err });\n  }\n  if (!request) {\n    return res.status(404).send({success: false, msg: 'Request not found for request_id '+ request_id + ' and id_project '+ id_project});\n  }\n\n  if (writer_id.startsWith(\"bot_\")){\n      winston.debug('Writer  writer_id starts with bot_');\n      return res.status(500).send({success: false, msg: 'Writer  writer_id starts with bot_' });\n  }\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(writer_id);\n  winston.debug(\"isObjectId:\"+ isObjectId);\n\n  var queryProjectUser = {id_project: request.id_project, status: \"active\"};\n\n  if (isObjectId) {\n    queryProjectUser.id_user = writer_id;\n  }else {\n    queryProjectUser.uuid_user = writer_id;\n  }\n\n  return Project_user.findOne(queryProjectUser, function(err, pu){\n    if (err){\n      winston.error(err);\n      return res.status(500).send({success: false, msg: 'Error finding pu', err:err });\n    }\n    winston.debug(\"typing pu\", pu);\n\n    if (!pu) {\n      return winston.warn(\"Project_user for typing not found\", queryProjectUser);\n    }\n    var attr = {recipient_id: recipient_id, writer_id:writer_id, data: data};\n\n\n    //       emit(name, attributes, id_project, project_user, createdBy, status, user) {\n    eventService.emit(\"typing.start\", attr, request.id_project, pu._id, writer_id).then(function (data) {\n      // eventService.emit(\"typing.start\", attr, request.id_project, pu._id, writer_id, \"volatile\").then(function (data) {      \n      return res.json(data);\n    });\n  });\n \n\n\n  });\n  \n\n}\n\n// curl -X POST -H 'Content-Type:application/json'  -d '{\"event_type\":\"presence-change\",\"presence\":\"online\",\"createdAt\":1596448898776,\"app_id\":\"tilechat\",\"user_id\":\"a9109ed4-ceda-4118-b934-c9b83c2eaf12\",\"data\":true}' http://localhost:3000/chat21/requests\n\n\nelse if (req.body.event_type == \"presence-change\") {\n\n  winston.debug(\"event_type\",\"presence-change\");\n\n  winston.debug(\"req.body\", req.body);\n  \n\n  var data = req.body.data;\n  winston.debug(\"data\", data);\n  \n  var user_id = req.body.user_id;\n  winston.debug(\"user_id: \"+ user_id);\n\n  var presence = req.body.presence;\n  winston.debug(\"presence: \"+  presence);\n\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(user_id);\n  winston.debug(\"isObjectId:\"+ isObjectId);\n\n  var queryProjectUser = {status: \"active\"};\n\n  if (isObjectId) {\n    queryProjectUser.id_user = user_id;\n  }else {\n    queryProjectUser.uuid_user = user_id;\n  }\n  winston.debug(\"queryProjectUser:\", queryProjectUser);\n\n\n\n  Project_user.find(queryProjectUser, function (err, project_users) {\n    // Project_user.updateMany(queryProjectUser, update, function (err, updatedProject_user) {\n    if (err) {\n      winston.error(\"Error gettting project_user\", err);\n      return res.status(500).send({ success: false, msg: 'Error getting objects.' });\n    }\n    if (!project_users) {\n      winston.warn('Error getting Project_user.' );\n      return res.status(404).send({ success: false, msg: 'Error getting Project_user.' });\n    }\n    winston.debug(\"project_users:\", project_users);\n\n\n    project_users.forEach(project_user => { \n      winston.debug(\"project_user:\", project_user);\n      var update = {status:presence};\n      update.changedAt = new Date();\n\n      project_user.presence = update\n\n      \n      project_user.save(function (err, savedProjectUser) {\n        if (err) {\n         winston.error('Error saving project_user ', err)\n        //  return res.status(500).send({ success: false, msg: 'Error getting objects.' });         \n        } else {\n        winston.debug('project_user saved ', savedProjectUser);\n\n\n        savedProjectUser\n          // .populate({path:'id_user', select:{'firstname':1, 'lastname':1}})\n          // .populate({path:'id_project', select:{'settings':1}})\n          .populate([{path:'id_user', select:{'firstname':1, 'lastname':1}}, {path:'id_project'}])\n          // .populate('id_user id_project')\n                .execPopulate(function (err, updatedProject_userPopulated){    \n                if (err) {\n                  winston.error(\"Error gettting updatedProject_userPopulated for update\", err);\n                  // continue;\n                  // return res.status(500).send({ success: false, msg: \"Error gettting updatedProject_userPopulated for update\" }); \n                } else {\n\n                  if (!updatedProject_userPopulated) {\n                    winston.warn('Error getting updatedProject_userPopulated.',savedProjectUser );\n                    // continue;\n                    // return res.status(404).send({ success: false, msg: 'Error getting updatedProject_userPopulated.' });\n                  } else {\n\n                    winston.debug(\"updatedProject_userPopulated:\", updatedProject_userPopulated);\n                    var pu = updatedProject_userPopulated.toJSON();\n          \n                    // urgente Cannot read property '_id' of null at /usr/src/app/channels/chat21/chat21WebHook.js:663:68 a\n                    if (!updatedProject_userPopulated.id_project) {\n                      winston.debug('Error updatedProject_userPopulated.id_project not found.',{updatedProject_userPopulated:updatedProject_userPopulated, savedProjectUser:savedProjectUser,project_user:project_user});\n                      // return res.status(404).send({ success: false, msg: 'Error updatedProject_userPopulated.id_project not found.' });\n                      // continue;\n                    } else {\n                      pu.id_project =  updatedProject_userPopulated.id_project._id;\n          \n            \n                      if (updatedProject_userPopulated.id_user) {\n                        pu.id_user = updatedProject_userPopulated.id_user._id;\n                      }else {\n                        // it's uuid_user user\n                      }\n                      \n                      pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, updatedProject_userPopulated.id_project.settings && updatedProject_userPopulated.id_project.settings.max_agent_assigned_chat);\n            \n                      // winston.info(\"pu:\", pu);\n            \n            \n                      authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req, skipArchive:true});\n                    // winston.info(\"after pu:\");\n                    }\n                  \n          \n\n                  }   \n                  \n                }       \n                \n                \n\n\n            });\n        }\n\n\n    }); \n\n\n    \n    });\n \n    if (project_users && project_users.length>0) {\n      winston.verbose(\"Presence changed for user_id : \"+  user_id + \" and presence \"+ presence +\". Updated \" + project_users.length + \" project users\");\n    }\n    \n\n    return res.json({ok:true});\n\n\n  });\n  \n\n}\n\nelse if (req.body.event_type == \"new-group\") {\n  winston.debug(\"new-group is not implemented\");\n  res.json(\"new-group event_type is not implemented\");\n}\nelse {\n  winston.debug(\"Chat21WebHook error event_type not implemented\", req.body);\n  res.json(\"Not implemented\");\n}\n\n  \n\n});\n\n\n\n\n\n\n\n\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "channels/chat21/configRoute.js",
    "content": "var express = require('express');\nvar router = express.Router();\n// var Setting = require(\"../models/setting\");\n\n\n\nrouter.get('/', function (req, res) {\n  console.log('Config');\n  \n    var config = {};\n\n    if (process.env.FIREBASE_APIKEY) {\n      config.apiKey = process.env.FIREBASE_APIKEY;\n    }\n\n    if (process.env.FIREBASE_AUTHDOMAIN) {\n      config.authDomain = process.env.FIREBASE_AUTHDOMAIN;\n    }\n\n    if (process.env.FIREBASE_DATABASEURL) {\n      config.databaseURL = process.env.FIREBASE_DATABASEURL;\n    }\n\n    if (process.env.FIREBASE_PROJECT_ID) {\n      config.projectId = process.env.FIREBASE_PROJECT_ID;\n    }\n\n    if (process.env.FIREBASE_STORAGEBUCKET) {\n      config.storageBucket = process.env.FIREBASE_STORAGEBUCKET;\n    }\n\n    if (process.env.FIREBASE_MESSAGINGSENDERID) {\n      config.messagingSenderId = process.env.FIREBASE_MESSAGINGSENDERID;\n    }\n  \n    if (process.env.CHAT21_URL) {\n      config.chat21ApiUrl = process.env.CHAT21_URL;\n    }\n  \n  \n     \n    res.json(config);\n  \n});\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "channels/chat21/firebaseConfig.js",
    "content": "module.exports = {\n  'databaseUrl': 'https://chat-v2-dev.firebaseio.com'\n};\n"
  },
  {
    "path": "channels/chat21/firebaseConnector.js",
    "content": "'use strict';\n\nvar winston = require('../../config/winston');\n\nconst MaskData = require(\"maskdata\");\n\nconst maskOptions = {\n  // Character to mask the data. default value is '*'\n  maskWith : \"*\",\n  // If the starting 'n' digits needs to be unmasked\n  // Default value is 4\n  unmaskedStartDigits : 30, //Should be positive Integer\n  //If the ending 'n' digits needs to be unmasked\n  // Default value is 1\n  unmaskedEndDigits : 30 // Should be positive Integer\n  };\n\n\nvar firebaseConfig = require('./firebaseConfig');\n\nvar firebaseConfigFilePath = process.env.FIREBASE_CONFIG_FILE || '../../.firebasekey.json';\n\nwinston.info('Chat21 channel- FirebaseConnector firebaseConfig.databaseURL: '+ firebaseConfig.databaseUrl);\n\nvar private_key = process.env.FIREBASE_PRIVATE_KEY;\n\nvar maskedprivate_key;\nif (private_key) {\n  maskedprivate_key = MaskData.maskPhone(private_key, maskOptions);\n}else {\n  maskedprivate_key = private_key;\n}\n\nwinston.info('Chat21 channel - FirebaseConnector private_key: '+ maskedprivate_key);\n// var private_key_masked = private_key.replace(/\\d(?=\\d{4})/g, \"*\");\n// winston.info('firebaseConnector private_key:'+ private_key_masked);// <-- TODO obscure it\n\nvar client_email = process.env.FIREBASE_CLIENT_EMAIL;\n\nvar maskedclient_email;\nif (client_email) {\n  maskedclient_email = MaskData.maskEmail2(client_email, maskOptions);\n} else {\n  maskedclient_email= client_email;\n}\nwinston.info('Chat21 channel - FirebaseConnector client_email: '+ maskedclient_email);\n\n\nvar firebase_project_id = process.env.FIREBASE_PROJECT_ID;\nwinston.info('Chat21 channel - FirebaseConnector firebase_project_id: '+ firebase_project_id);\n\n// https://stackoverflow.com/questions/41287108/deploying-firebase-app-with-service-account-to-heroku-environment-variables-wit\nvar serviceAccount;\n\nif (!private_key || !client_email) {\n  serviceAccount = require(firebaseConfigFilePath);\n  winston.debug('FIREBASE_PRIVATE_KEY and FIREBASE_CLIENT_EMAIL not specified, falling back to serviceAccountFromKey from path',firebaseConfigFilePath, serviceAccount);\n}else {\n    serviceAccount = {\n        \"private_key\": private_key.replace(/\\\\n/g, '\\n'),\n        \"client_email\": client_email,\n        \"project_id\": firebase_project_id,\n      };\n      winston.debug('firebase serviceAccount from env', serviceAccount);\n}\n\nconst admin = require('firebase-admin');\n// admin.initializeApp(functions.config().firebase);\n\n\n  admin.initializeApp({\n    credential: admin.credential.cert(serviceAccount),\n    databaseURL: firebaseConfig.databaseURL\n  });     \n\n//var firestore = admin.firestore();\n//end firestore\n\n\n\nmodule.exports = admin;\n"
  },
  {
    "path": "channels/chat21/firebaseService.js",
    "content": "var admin = require('./firebaseConnector');\n\n\nvar winston = require('../../config/winston');\n\nclass FirebaseService {\n\n    createCustomToken(uid) {\n\n        return new Promise(function (resolve, reject) {          \n            winston.debug(\"createCustomToken for uid\", uid);\n              const tokenPromise = admin.auth().createCustomToken(uid);\n              return tokenPromise.then(token => {\n                winston.debug('Created Custom token for UID \"', uid, '\" Token:', token);\n                return resolve(token);\n              });\n              \n        });\n    }\n\n    createCustomTokenWithAttribute(uid, customAttributes) {\n\n      return new Promise(function (resolve, reject) {          \n          winston.debug(\"createCustomToken for uid\", uid, \" and customAttributes : \",customAttributes);\n            const tokenPromise = admin.auth().createCustomToken(uid, customAttributes);\n            return tokenPromise.then(token => {\n              winston.debug('Created Custom token for UID \"', uid, '\" Token:', token);\n              return resolve(token);\n            });\n            \n      });\n  }\n\n\n}\n\nvar firebaseService = new FirebaseService();\n\n\nmodule.exports = firebaseService;"
  },
  {
    "path": "channels/chat21/firebaseauth.js",
    "content": "\n'use strict';\n\nconst express = require('express');\nvar router = express.Router();\nvar firebaseService = require(\"./firebaseService\");\nvar winston = require('../../config/winston');\n\n\n\nrouter.post('/createCustomToken', function (req, res) {\n        // var uid = req.projectid + '-' + req.user.id;\n        var uid = req.user.id;\n        winston.debug(\"uid\",uid);\n\n        firebaseService.createCustomToken(uid).then(customAuthToken => {\n        \n            return res.status(200).send(customAuthToken);   \n        })\n        .catch(err => {\n            const ret = {\n                error_message: 'Authentication error: Cannot verify access token.',\n                err: err\n            };\n                return res.status(403).send(ret);\n        });\n                 \n  });\n \n  \n\n\n  module.exports = router;\n\n"
  },
  {
    "path": "channels/chat21/nativeauth.js",
    "content": "\n'use strict';\n\nconst express = require('express');\nvar router = express.Router();\nvar winston = require('../../config/winston');\nconst uuidv4 = require('uuid/v4');\nconst jwt = require(\"jsonwebtoken\")\n\nconst MaskData = require(\"maskdata\");\n\nconst maskPhoneOptions = {\n  // Character to mask the data. default value is '*'\n  maskWith : \"*\",\n  // If the starting 'n' digits needs to be unmasked\n  // Default value is 4\n  unmaskedStartDigits : 3, //Should be positive Integer\n  //If the ending 'n' digits needs to be unmasked\n  // Default value is 1\n  unmaskedEndDigits : 3 // Should be positive Integer\n  };\n\n\nconst jwtSecret = process.env.CHAT21_JWT_SECRET || \"tokenKey\";\n\nconst masked_jwtSecret = MaskData.maskPhone(jwtSecret, maskPhoneOptions);\n\n\nwinston.info(\"Chat21 Native channel jwtSecret: \"+ masked_jwtSecret);\n\n\n\nrouter.post('/createCustomToken', function (req, res) {\n\n        var userid = req.user.id;\n        winston.debug(\"userid\",userid);\n\n        var user = req.user;\n\n        const appid = \"tilechat\";\n\n        const scope = [\n            `rabbitmq.read:*/*/apps.${appid}.users.${userid}.*`,\n            `rabbitmq.write:*/*/apps.${appid}.users.${userid}.*`,\n            `rabbitmq.write:*/*/apps.${appid}.outgoing.users.${userid}.*`,\n            'rabbitmq.configure:*/*/*'\n        ]\n    \n        const now = Math.round(new Date().getTime()/1000);\n        // console.log(\"now: \", now)\n        const exp = now + 60 * 60 * 24 * 30;\n\n        var payload = {\n            \"jti\": uuidv4(),\n            \"sub\": user._id,\n            scope: scope,\n            \"client_id\": user._id, //\"rabbit_client\", SEMBRA SIA QUESTO LO USER-ID\n            \"cid\": user._id, //\"rabbit_client\",\n            \"azp\": user._id, //\"rabbit_client\",\n            // \"grant_type\": \"password\", //\"password\", // client_credentials // REMOVED 2\n            \"user_id\": user._id,\n            \"app_id\": appid,\n            // \"origin\": \"uaa\", // REMOVED 2\n            // \"user_name\": user._id, // REMOVED 2\n            // \"email\": user.email,\n            // \"auth_time\": now, // REMOVED 2\n            // \"rev_sig\": \"d5cf8503\",\n            \"iat\": now,\n            \"exp\": exp, // IF REMOVED TOKEN NEVER EXPIRES?\n            // \"iss\": \"http://localhost:8080/uaa/oauth/token\", // REMOVED 2\n            // \"zid\": \"uaa\", // REMOVED 2\n            \"aud\": [\n                \"rabbitmq\",\n                user._id\n            ],\n            // \"jku\": \"https://localhost:8080/uaa/token_keys\", // REMOVED 2\n            \"kid\": \"tiledesk-key\", //\"legacy-token-key\",\n            \"tiledesk_api_roles\": \"user\"\n        }\n        winston.debug(\"payload:\\n\", payload)\n        var token = jwt.sign(\n            payload,\n            jwtSecret,\n            {\n                \"algorithm\": \"HS256\"\n            }\n        );\n        const result = {\n            userid: user._id,\n            fullname: user.fullName,\n            firstname: user.firstname,\n            lastname: user.lastname,\n            token: token\n        }\n        return res.status(200).send(result);   \n\n\n  });\n \n\n\n  module.exports = router;\n\n"
  },
  {
    "path": "channels/chat21/package.json",
    "content": "{\n  \"name\": \"@tiledesk/tiledesk-chat21-app\",\n  \"description\": \"The Tiledesk Chat21 module\",\n  \"version\": \"1.1.8\",\n  \"private\": false,\n  \"author\": \"Andrea Leo - Frontiere21 SRL\",\n  \"license\": \"AGPL-3.0\",\n  \"homepage\": \"https://www.tiledesk.com\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/Tiledesk/tiledesk-server\"\n  },\n  \"dependencies\": {\n    \"@chat21/chat21-node-sdk\": \"^1.1.8\",\n    \"winston\": \"^3.3.3\",\n    \"firebase-admin\": \"^9.5.0\"\n  },\n  \"devDependencies\": {}\n}\n"
  },
  {
    "path": "channels/chat21/test-int/chat21Handler.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nrequire('dotenv').config();\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../../../config/database');\nvar Request = require('../../../models/request');\n\nvar mongoose = require('mongoose');\nvar winston = require('../../../config/winston');\nvar MessageConstants = require(\"../../../models/messageConstants\");\nrequire('../../../services/mongoose-cache-fn')(mongoose);\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\n\nvar userService = require('../../../services/userService');\nvar projectService = require('../../../services/projectService');\nvar leadService = require('../../../services/leadService');\nvar requestService = require('../../../services/requestService');\nvar messageService = require('../../../services/messageService');\n\nconst chat21Event = require('../chat21Event');\nconst messageEvent = require('../../../event/messageEvent');\n\nvar chat21Config = require('../chat21Config');\n\nvar adminToken = process.env.CHAT21_ADMIN_TOKEN || chat21Config.adminToken \nwinston.info('Test Chat21Handler adminToken: ' + adminToken);\n\nvar chat21 = require('../chat21Client');\n\nvar chat21Handler = require('../chat21Handler');\nchat21Handler.listen();\n\ndescribe('Chat21Handler', function () {\n  \n  before(function() {\n    // runs before all tests in this block\n\n  \n\n  });\n\n \n\n  it('creategroup', function (done) {\n\n\n\n\n\n\n    chat21.auth.setAdminToken(adminToken);\n    winston.info(\"create group\");\n    chat21.groups.create('test1', ['12345'], {attr:'a1'}).then(function(data){\n            winston.info(\"group created: \" + data);\n                         done();              \n\n        }).catch(function(err) {\n            winston.error(\"Error creating chat21 group \", err);\n        });\n\n});\n\n\n\n// mocha channels/chat21/test-int/chat21Handler.js   --grep 'createRequest'\n  it('createRequest', function (done) {\n  \n\n    var email = \"test-createRequest-chat21handler\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n     projectService.create(\"createWithId\", savedUser._id).then(function(savedProject) {\n      leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n\n       \n        var counter = 0;\n        chat21Event.on('group.create', function(data){\n          counter++;\n          winston.info(\"group created\", data);\n\n          // if (data){\n            if (counter==1) {\n              done();\n            }\n            \n          // }\n         \n        });\n\n      // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n       requestService.createWithId(\"request_id-chat21-createRequest\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n          winston.debug(\"resolve\", savedRequest.toObject());\n          expect(savedRequest.request_id).to.equal(\"request_id-chat21-createRequest\");                       \n\n             messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"hello\",\n                                        savedProject._id, savedUser._id).then(function(savedMessage){\n                                            expect(savedMessage.text).to.equal(\"hello\");     \n\n\n                                            \n\n\n                                        });\n\n        });\n    });\n  });\n  });\n\n  }).timeout(20000);\n\n\n\n  // it('firstMessage', function (done) {\n\n\n\n  //   var email = \"test-createRequest-chat21handler\" + Date.now() + \"@email.com\";\n  //   var pwd = \"pwd\";\n\n  //   userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n  //    projectService.create(\"createWithId\", savedUser._id).then(function(savedProject) {\n  //     leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n            \n  //     // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n  //      requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n  //         winston.debug(\"resolve\", savedRequest.toObject());\n  //         expect(savedRequest.request_id).to.equal(\"request_id1\");                       \n\n\n  //         // messageEvent.on('message.create.first',function(message) {\n  //         //   expect(message.text).to.equal(\"hello\");   \n  //         //     done();\n  //         // });\n  //         chat21Event.on('firestore.first_message', function(firestoreUpdate){\n  //                   winston.info(\"firestore.first_message created\", firestoreUpdate);\n  //                   winston.info(\"savedUser._id\", savedUser._id);\n  //                 if (firestoreUpdate.first_message.sender === savedUser._id.toString()) {\n  //                   expect(firestoreUpdate.first_message.attributes).to.not.equal(null);                                               \n  //                   done();\n  //                 }\n                   \n  //                 });\n\n\n\n  //           //  create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes)\n  //            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"first_text\",\n  //                                       savedProject._id, savedUser._id,MessageConstants.CHAT_MESSAGE_STATUS.SENDING, {attr1:'attr1'}).then(function(savedMessage){\n  //                                           expect(savedMessage.text).to.equal(\"hello\");                                               \n\n  //           });\n\n  //       });\n  //   });\n  // });\n  // });\n\n  // }).timeout(20000);\n\n\n});"
  },
  {
    "path": "channels/chat21/test-int/chat21WebHook.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nlet mongoose = require(\"mongoose\");\nvar Request = require(\"../../../models/request\");\nvar projectService = require('../../../services/projectService');\nvar requestService = require('../../../services/requestService');\nvar leadService = require('../../../services/leadService');\nvar userService = require('../../../services/userService');\n\nvar Lead = require('../../../models/lead');\nvar Message = require('../../../models/message');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../../../app');\nlet should = chai.should();\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\n// run with \n// npm test -- ./test/chat21RequestRoute.js\n\n//Our parent block\ndescribe('Chat21WebHook', () => {\n    // beforeEach((done) => { //Before each test we empty the database\n    //     Book.remove({}, (err) => { \n    //        done();           \n    //     });        \n    // });\n/*\n  * Test the /GET route\n  */\n\n  describe('post', () => {\n \n    var userid = \"5badfe5d553d1844ad654072\";\n\n\n      it('new-messageWithoutEmail', (done) => {\n\n        var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n    \n        userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n          projectService.create(\"test-new-message\", savedUser._id).then(function(savedProject) {\n\n            var request_id = \"support-group-\"+savedProject._id;\n            let webhookContent = {\"event_type\": \"new-message\", \"data\":{\"sender\":savedUser._id, \"sender_fullname\": \"sender_fullname\", \n            \"recipient\":request_id, \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\", \n            \"attributes\": {\"projectId\":savedProject._id} }\n               };\n\n            chai.request(server)\n                .post('/chat21/requests')\n                .send(webhookContent)\n                .end((err, res) => {\n                    console.log(\"res.body\",  res.body);\n                    res.should.have.status(200);\n                    res.body.should.be.a('object');\n                    res.body.should.have.property('request_id').eql(request_id);\n                    // res.body.should.have.property('requester_id').eql(\"sender\");\n                    expect(res.body.id_project).to.equal(savedProject._id.toString());\n                    expect(res.body.participants).to.have.lengthOf(1);       \n\n                    // expect(res.body.messages_count).to.equal(1);     \n                    \n                    // expect(request.waiting_time).to.not.equal(null);\n                    // expect(request.waiting_time).to.gt(0);\n                    Message.findOne({recipient : request_id, id_project: savedProject._id,text:\"text\"}, function(err, message){\n\n                        expect(message.sender).to.equal(savedUser._id.toString());    \n                        expect(message.recipient).to.equal(request_id);     \n                        expect(message.attributes.projectId).to.equal(savedProject._id.toString());     \n                        Lead.findById(res.body.requester_id, function (err, lead){\n                            expect(lead.fullname).to.equal(\"sender_fullname\");   \n                            assert(lead.email == null)\n                            \n                            done();\n                        });\n\n                        \n                    });\n                    \n                   \n                });\n          });\n        });\n    });\n\n // mocha channels/chat21/test-int/chat21WebHook.js   --grep 'new-messageWithEmailOnly'\n        it('new-messageWithEmailOnly', (done) => {\n\n            var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n            projectService.create(\"test-new-message\", savedUser._id).then(function(savedProject) {\n  \n              var request_id = \"support-group-\"+savedProject._id;\n              let webhookContent = {\"event_type\": \"new-message\", \n                \"data\":{\n                    \"sender\":savedUser._id, \"sender_fullname\": \"sender_fullname\", \n                    \"recipient\":request_id, \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\", \n                    \"attributes\": {\"projectId\":savedProject._id, \"userEmail\": \"user@email.com\", \"userFullname\": \"userFullname\"},\n                    // elimina\n                    \"senderAuthInfo\":\n                            {\n                                \"authType\" : \"USER\",\n                                \"authVar\" : {\n                                \"token\" : {\n                                    \"aud\" : \"chat-v2-dev\",\n                                    \"auth_time\" : 1542282861,\n                                    \"exp\" : 1542389465,\n                                    \"firebase\" : {\n                                    \"sign_in_provider\" : \"anonymous\"\n                                    },\n                                    \"iat\" : 1542385865,\n                                    \"iss\" : \"https://securetoken.google.com/chat-v2-dev\",\n                                    \"provider_id\" : \"anonymous\",\n                                    \"sub\" : \"yfSkr0RLgsRIVGNoHnJT8y4wIRj1\",\n                                    \"user_id\" : \"yfSkr0RLgsRIVGNoHnJT8y4wIRj1\"\n                                },\n                                \"uid\" : \"yfSkr0RLgsRIVGNoHnJT8y4wIRj1\"\n                                }\n                            }\n                      \n\n                    }\n                 };\n  \n              chai.request(server)\n                  .post('/chat21/requests')\n                  .send(webhookContent)\n                  .end((err, res) => {\n                      console.log(\"res.body\",  res.body);\n                      res.should.have.status(200);\n                      res.body.should.be.a('object');\n                      res.body.should.have.property('request_id').eql(request_id);\n                      // res.body.should.have.property('requester_id').eql(\"sender\");\n                      expect(res.body.id_project).to.equal(savedProject._id.toString());\n                      expect(res.body.participants).to.have.lengthOf(1);       \n\n                    //   expect(res.body.messages_count).to.equal(1);     \n\n                      // expect(request.waiting_time).to.not.equal(null);\n                      // expect(request.waiting_time).to.gt(0);\n                      Message.findOne({recipient : request_id, id_project: savedProject._id,text:\"text\"}, function(err, message){\n\n                        expect(message.sender).to.equal(savedUser._id.toString());    \n                        expect(message.recipient).to.equal(request_id);     \n                        Lead.findById(res.body.lead, function (err, lead){\n                            console.log(\"lead.attributes\", JSON.stringify(lead.attributes));\n                            expect(lead.fullname).to.equal(\"userFullname\");   \n                            expect(lead.email).to.equal(\"user@email.com\");   \n                            expect(lead.attributes.projectId).to.equal(savedProject._id.toString());   \n                            expect(lead.attributes.userEmail).to.equal(\"user@email.com\");   \n                            expect(lead.attributes.senderAuthInfo.authVar.token.provider_id).to.equal(\"anonymous\");   \n                            \n                            done();\n                        });\n                     });\n  \n                     \n                  });\n            });\n          });\n        });\n\n        \n//        mocha channels/chat21/test-int/chat21WebHook.js   --grep 'new-messageWithEmailAndFullnameAndRequestAlreadyExists'\n          it('new-messageWithEmailAndFullnameAndRequestAlreadyExists', (done) => {\n\n            var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n            // projectService.create(\"test-new-message\", savedUser._id).then(function(savedProject) {\n            projectService.createAndReturnProjectAndProjectUser(\"test-new-message\", savedUser._id).then(function(savedProjectAndPU) {\n                var savedProject = savedProjectAndPU.project;    \n\n                var request_id = \"support-group-\"+savedProject._id;\n\n                leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n                        // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy) {\n                        // requestService.createWithId(request_id, createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                            \n                            var request = {\n                                request_id:request_id, project_user_id:savedProjectAndPU.project_user._id, lead_id:createdLead._id, \n                                id_project:savedProject._id, first_text: \"first_text\",\n                                lead:createdLead, requester: savedProjectAndPU.project_user };\n          \n                            requestService.create(request).then(function(savedRequest) {\n\n                            let webhookContent = {\"event_type\": \"new-message\", \"data\":{\"sender\":savedUser._id, \"sender_fullname\": \"sender_fullname\", \n                            \"recipient\":request_id, \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\", \n                            \"attributes\": {\"projectId\":savedProject._id, \"userEmail\": createdLead.email, \"userFullname\": createdLead.fullname} }\n                                };\n                // DA SISTEMARE\n                            chai.request(server)\n                                .post('/chat21/requests')\n                                .send(webhookContent)\n                                .end((err, res) => {\n                                    console.log(\"res.body\",  res.body);\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    res.body.should.have.property('request_id').eql(request_id);\n                                    // res.body.should.have.property('requester_id').eql(createdLead._id.toString());\n                                    expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                    expect(res.body.participants).to.have.lengthOf(1);       \n\n                                    // expect(res.body.messages_count).to.equal(1);     \n\n                                    // expect(request.waiting_time).to.not.equal(null);\n                                    // expect(request.waiting_time).to.gt(0);\n                                    Message.findOne({recipient : request_id, id_project: savedProject._id,text:\"text\"}, function(err, message){\n\n                                        expect(message.sender).to.equal(savedUser._id.toString());    \n                                        expect(message.recipient).to.equal(request_id);     \n                                        Lead.findById(res.body.lead, function (err, lead){\n                                            expect(lead.fullname).to.equal(\"leadfullname\");   \n                                            expect(lead.email).to.equal(\"email@email.com\");   \n                                            done();\n                                        });\n                                    });\n                \n                                    \n                                });\n\n                                });\n                    });\n            });\n          });\n        });\n    \n\n\n\n //       mocha channels/chat21/test-int/chat21WebHook.js   --grep 'new-messageWithEmailAndFullnameAndRequestAlreadyExistAndNOProjectID'\n          it('new-messageWithEmailAndFullnameAndRequestAlreadyExistAndNOProjectID', (done) => {\n\n            var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n\n            // projectService.create(\"test-new-message\", savedUser._id).then(function(savedProject) {\n            projectService.createAndReturnProjectAndProjectUser(\"test-new-message\", savedUser._id).then(function(savedProjectAndPU) {\n                var savedProject = savedProjectAndPU.project;    \n\n                    \n                var request_id = \"support-group-\"+savedProject._id;\n\n                leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n                        // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy) {\n                        // requestService.createWithId(request_id, createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n\n                            var request = {\n                                request_id:request_id, project_user_id:savedProjectAndPU.project_user._id, lead_id:createdLead._id, \n                                id_project:savedProject._id, first_text: \"first_text\",\n                                lead:createdLead, requester: savedProjectAndPU.project_user };\n          \n                            requestService.create(request).then(function(savedRequest) {\n\n                            \n                            let webhookContent = {\"event_type\": \"new-message\", \n                                \"data\":{\"sender\":savedUser._id, \"sender_fullname\": \"sender_fullname\", \"recipient\":request_id, \"recipient_fullname\":\"Andrea Leo\",\"text\":\"text\"}\n                                };\n                \n                            chai.request(server)\n                                .post('/chat21/requests')\n                                .send(webhookContent)\n                                .end((err, res) => {\n                                    console.log(\"res.body\",  res.body);\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    res.body.should.have.property('request_id').eql(request_id);\n                                    // res.body.should.have.property('requester_id').eql(createdLead._id.toString());\n                                    expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                    expect(res.body.participants).to.have.lengthOf(1);       \n\n                                    // expect(res.body.messages_count).to.equal(1);     \n\n                                    // expect(request.waiting_time).to.not.equal(null);\n                                    // expect(request.waiting_time).to.gt(0);\n                                    Message.findOne({recipient : request_id, id_project: savedProject._id,text:\"text\"}, function(err, message){\n\n                                        expect(message.sender).to.equal(savedUser._id.toString());    \n                                        expect(message.recipient).to.equal(request_id);     \n                                        Lead.findById(res.body.lead, function (err, lead){\n                                            expect(lead.fullname).to.equal(\"leadfullname\");   \n                                            expect(lead.email).to.equal(\"email@email.com\");   \n                                            done();\n                                        });\n                                    });\n                \n                                    \n                                });\n\n                                });\n                    });\n            });\n          });\n        });\n\n//       mocha channels/chat21/test-int/chat21WebHook.js   --grep 'joinmember'\n        it('joinmember', (done) => {\n\n\n            var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n\n            // projectService.create(\"test-join-member\", userid).then(function(savedProject) {\n                projectService.createAndReturnProjectAndProjectUser(\"test-join-member\", savedUser._id).then(function(savedProjectAndPU) {\n                    var savedProject = savedProjectAndPU.project;    \n\n                // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {\n                // requestService.createWithId(\"join-member\", \"join-member-requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n\n                    var request_id = \"support-group-\"+savedProject._id;\n\n                    leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n\n                    var request = {\n                        request_id:request_id, project_user_id:savedProjectAndPU.project_user._id, lead_id:createdLead._id, \n                        id_project:savedProject._id, first_text: \"join-member-requester_id1\",\n                        lead:createdLead, requester: savedProjectAndPU.project_user };\n  \n                    requestService.create(request).then(function(savedRequest) {\n\n    \n                    var webhookContent =     { \"event_type\": 'join-member', \"createdAt\": 1538156223681, \"group_id\": savedRequest.request_id, \n                            \"app_id\": 'tilechat', \"member_id\": savedUser._id, \"data\": { \"member_id\": savedUser._id, \"group\":  { \"createdOn\": 1538156223311,\n                        \"iconURL\": 'NOICON', \"members\": [Object], \"name\": 'Bash', \"owner\": 'system', 'attributes': {\"projectId\":savedProject._id} } } }\n                        \n            \n                    chai.request(server)\n                        .post('/chat21/requests')\n                        .send(webhookContent)\n                        .end((err, res) => {\n                            //console.log(\"res\",  res);\n                            console.log(\"res.body\",  res.body);\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            res.body.should.have.property('status').eql(200);\n                            \n\n                            res.body.should.have.property('participants').to.have.lengthOf(1);\n                            // res.body.should.have.property('participants').contains(\"agentid1\");\n                            res.body.should.have.property('participants').contains(savedUser._id.toString());\n                           \n                        done();\n                        });\n\n                        \n                });\n                });\n        });\n    });\n\n    });\n\n    // A cosa serve ?\n    // // mocha channels/chat21/test-int/chat21WebHook.js   --grep 'butnottherequesterid'\n    //     it('join-member-butnottherequesterid', (done) => {\n\n    //         var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    //         var pwd = \"pwd\";\n\n    //         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n    //         projectService.create(\"test-join-member\", savedUser._id).then(function(savedProject) {\n    //             leadService.createIfNotExistsWithLeadId(\"requester_id1-join-member-butnottherequesterid\", \"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n    //             requestService.createWithId(\"join-member-requestid\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n    \n    //                 var webhookContent =     { \"event_type\": 'join-member', \"createdAt\": 1538156223681, \"group_id\": savedRequest.request_id, \n    //                         \"app_id\": 'tilechat', \"member_id\": savedUser._id, \"data\": { \"member_id\": savedUser._id, \"group\":  { \"createdOn\": 1538156223311,\n    //                     \"iconURL\": 'NOICON', \"members\": [Object], \"name\": 'Bash', \"owner\": 'system', 'attributes': {\"projectId\":savedProject._id} } } }\n                        \n            \n    //                 chai.request(server)\n    //                     .post('/chat21/requests')\n    //                     .send(webhookContent)\n    //                     .end((err, res) => {\n    //                         //console.log(\"res\",  res);\n    //                         console.log(\"res.body\",  res.body);\n    //                         res.should.have.status(400);\n                            \n                           \n    //                     done();\n    //                     });\n    //                 });\n                        \n    //             });\n    //             });\n    //     });\n\n    // });\n\n\n\n\n\n    // mocha channels/chat21/test-int/chat21WebHook.js   --grep 'leave-member'\n        it('leave-member', (done) => {\n\n            projectService.create(\"test-leave-member\", userid).then(function(savedProject) {\n                requestService.createWithId(\"leave-member\", \"requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n    \n                    var webhookContent =     { \"event_type\": 'leave-member', \"createdAt\": 1538156223681, \"group_id\": savedRequest.request_id, \n                            \"app_id\": 'tilechat', \"member_id\": userid, \"id_project\":savedProject._id, \"data\": { \"member_id\": userid, \"group\":  { \"createdOn\": 1538156223311,\n                        \"iconURL\": 'NOICON', \"members\": [Object], \"name\": 'Bash', \"owner\": 'system', 'attributes': {\"projectId\":savedProject._id} } } }\n                        \n            \n                    chai.request(server)\n                        .post('/chat21/requests')\n                        .send(webhookContent)\n                        .end((err, res) => {\n                            //console.log(\"res\",  res);\n                            console.log(\"res.body\",  res.body);\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            res.body.should.have.property('status').eql(100);\n                            \n\n                            res.body.should.have.property('participants').to.have.lengthOf(0);\n                            // res.body.should.have.property('participants').contains(\"agentid1\");\n                            // res.body.should.have.property('participants').contains(userid);\n                        \n                        done();\n                        });\n                });\n                });\n        });\n\n\n        // mocha channels/chat21/test-int/chat21WebHook.js   --grep 'deleted-archivedconversation'\n\n        it('deleted-archivedconversation', (done) => {\n\n            projectService.create(\"test-deleted-archivedconversation\", userid).then(function(savedProject) {\n                requestService.createWithId(\"support-group-test-deleted-archivedconversation\", \"requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n                    requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id).then(function(closedRequest) {\n\n                        \n\n                        var webhookContent =     { \"event_type\": 'deleted-archivedconversation', \"createdAt\": 1538156223681, \n                                \"app_id\": 'tilechat',\"user_id\": \"system\", \"recipient_id\": \"support-group-test-deleted-archivedconversation\",\n                                \"data\": {\"attributes\" : {\"projectId\" : savedProject._id} }};\n                            \n                \n                        chai.request(server)\n                            .post('/chat21/requests')\n                            .send(webhookContent)\n                            .end((err, res) => {\n                                console.log(\"res\",  res);\n                                console.log(\"res.body\",  res.body);\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n                                res.body.should.have.property('status').eql(200);\n                                \n\n                                res.body.should.have.property('participants').to.have.lengthOf(1);\n                                // res.body.should.have.property('participants').contains(\"agentid1\");\n                                // res.body.should.have.property('participants').contains(userid);\n                            \n                            done();\n                            });\n                    });\n                });\n            });\n        });\n\n\n\n\n\n\n\n\n\n \n\n\n\n\n\n\n\n\n\n\n\n\n\n});\n\n});\n\n\n"
  },
  {
    "path": "channels/chat21/tiledesk-util.js",
    "content": "/* \n    ver 0.8.3\n    Andrea Sponziello - (c) Tiledesk.com\n*/\nvar winston = require('../../config/winston');\n\nclass TiledeskUtil {    \n\n    /* Splits a message in multiple commands using the microlanguage\n    \\split:TIME\n    command \\split:TIME must stand on a line of his own as in the following example\n     ex.\n  \n    <<Hi!\n    \\split:1000\n    Please tell me your email>>\n  \n    Sends two messages delayed by 1 second\n    */\n    findSplits(text) {\n        var commands = []\n        const split_pattern = /^(\\\\split[:0-9]*)/mg //ex. \\split:500\n        var parts = text.split(split_pattern)\n        for (var i=0; i < parts.length; i++) {\n            let p = parts[i]\n            winston.debug(\"part: \" + p)\n            if (i % 2 != 0) {\n            // split command\n            winston.debug(\"split command: \" + p)\n            var split_parts = p.split(\":\")\n            var wait_time = 1000\n            if (split_parts.length == 2) {\n                wait_time = split_parts[1]\n            }\n            winston.debug(\"wait time: \" + wait_time)\n            var command = {}\n            command.type = \"wait\"\n            command.time = parseInt(wait_time, 10)\n            commands.push(command)\n            }\n            else {\n            // message command\n            var command = {}\n            command.type = \"message\"\n            command.text = p.trim()\n            commands.push(command)\n            // if ( i == parts.length -1 &&\n            //     result['fulfillmentMessages'] &&\n            //     result['fulfillmentMessages'][1] &&\n            //     result['fulfillmentMessages'][1].payload) {\n            //     command.payload = result['fulfillmentMessages'][1].payload\n            // }\n            }\n        }\n        return commands\n    }\n  \n    parseReply(text) {\n        let TEXT_KEY = 'text'\n        let TYPE_KEY = 'type'\n        let ATTRIBUTES_KEY = 'attributes'\n        let METADATA_KEY = \"metadata\"\n        let TYPE_IMAGE = 'image'\n        let TYPE_TEXT = 'text'\n        \n  \n        var reply = {\n            \"message\": {}\n        }\n        reply.message[TEXT_KEY] = text\n        reply.message[TYPE_KEY] = TYPE_TEXT\n      \n        // looks for images\n        // images are defined as a line starting with:\n        // \\image:IMAGE_URL\n        // or with optional size:\n        // \\image:100-100:http://image.com/image.gif\n        var image_pattern = /^\\\\image:.*/mg;\n        // console.log(\"Searching images with image_pattern: \", image_pattern)\n        var images = text.match(image_pattern);\n        // console.log(\"images: \", images)\n        if (images && images.length > 0) {\n          const image_text = images[0]\n          var text = text.replace(image_text,\"\").trim()\n          var image_url = image_text.replace(\"\\\\image:\", \"\")\n  \n          var width = 200\n          var height = 200\n          // parse image size (optional) ex: \\image:100-100:http://image.com/image.gif\n          let image_size_pattern = /^([0-9]*-[0-9]*):(.*)/;\n          let image_size_text = image_url.match(image_size_pattern)\n          if (image_size_text && image_size_text.length == 3) {\n            image_url = image_size_text[2]\n            let image_size = image_size_text[1]\n            winston.debug(\"size: \" + image_size)\n            winston.debug(\"imageì url: \" + image_url)\n            let split_pattern = /-/\n            let size_splits = image_size.split(split_pattern)\n            if (size_splits.length == 2) {\n              width = size_splits[0]\n              height = size_splits[1]\n            }\n          }\n          reply.message[TEXT_KEY] = text\n          reply.message[TYPE_KEY] = TYPE_IMAGE\n          reply.message[METADATA_KEY] = {\n            src: image_url,\n            width: width,\n            height: height\n          }\n        }\n      \n        // looks for bullet buttons\n        var button_pattern = /^\\*.*/mg; // button pattern is a line that starts with *TEXT_OF_BUTTON (every button on a line)\n        var text_buttons = text.match(button_pattern);\n        if (text_buttons) {\n          // ricava il testo rimuovendo i bottoni\n          var text_with_removed_buttons = text.replace(button_pattern,\"\").trim()\n          reply.message[TEXT_KEY] = text_with_removed_buttons\n          // estrae i bottoni\n          var buttons = []\n          text_buttons.forEach(element => {\n            var remove_extra_from_button = /^\\*/mg; // removes initial \"*\"\n            var button_text = element.replace(remove_extra_from_button, \"\").trim()\n            var button = {}\n            button[TYPE_KEY] = \"text\"\n            button[\"value\"] = button_text\n            buttons.push(button)\n            winston.debug(\"Added button: \" + button_text)\n          });\n          if (reply.message[ATTRIBUTES_KEY] == null) {\n            reply.message[ATTRIBUTES_KEY] = {}\n          }\n          reply.message[ATTRIBUTES_KEY][\"attachment\"] = {\n            type:\"template\",\n            buttons: buttons\n          }\n          text = text_with_removed_buttons\n        }\n  \n        // looks for a webhook url\n        var webhook_pattern = /^\\\\webhook:.*/mg; // webhooks are defined as a line starting with \\webhook:URL\n        var webhooks = text.match(webhook_pattern);\n        if (webhooks && webhooks.length > 0) {\n          const webhook_text = webhooks[0]\n          winston.debug(\"webhook_text: \" + webhook_text)\n          text = text.replace(webhook_text,\"\").trim()\n          const webhook_url = webhook_text.replace(\"\\\\webhook:\", \"\")\n          winston.debug(\"webhook_url \" + webhook_url)\n          reply.webhook = webhook_url\n        }\n  \n        return reply\n    }\n  \n  }\n  \n  var tiledeskUtil = new TiledeskUtil();\n  \n  module.exports = tiledeskUtil;"
  },
  {
    "path": "config/cache.js",
    "content": "module.exports = {\n    'defaultTTL':120,    \n  };\n  "
  },
  {
    "path": "config/database.js",
    "content": "module.exports = {\n  secret:'nodeauthsecret',\n  schemaVersion: 2111, \n  database: 'mongodb://localhost:27017/tiledesk',\n  databaselogs: 'mongodb://localhost:27017/tiledesk-logs',\n  databasetest: 'mongodb://localhost:27017/tiledesk-test'\n};\n"
  },
  {
    "path": "config/email.js",
    "content": "module.exports = {\n  'host':'smtp.mailgun.org',\n  'username': 'postmaster@mg.tiledesk.com',\n  'from': 'Tiledesk Notification <postmaster@mg.tiledesk.com>',\n  'bcc': '',\n  'baseUrl':'http://localhost:8081/dashboard',\n  'replyEnabled' : false,\n  'inboundDomain': 'tickets.tiledesk.com'\n};\n"
  },
  {
    "path": "config/global.js",
    "content": "module.exports = {\n  'apiUrl':'http://localhost:3000',\n  'organizationBaseUrl' : 'org.local',\n  'organizationEnabled' : false\n};\n"
  },
  {
    "path": "config/kb/embedding.js",
    "content": "module.exports = {\n    provider: process.env.EMBEDDINGS_PROVIDER || \"openai\",\n    name: process.env.EMBEDDINGS_NAME || \"text-embedding-ada-002\",\n    api_key: \"\",\n    dimension: Number(process.env.EMBEDDINGS_DIMENSION) || 1536,\n    url: process.env.EMBEDDINGS_URL\n}"
  },
  {
    "path": "config/kb/engine.hybrid.js",
    "content": "module.exports = {\n    name: process.env.VECTOR_STORE_NAME || 'pinecone',\n    type: process.env.INDEX_TYPE_HYBRID || process.env.PINECONE_TYPE_HYBRID || 'serverless',\n    apikey: process.env.VECTOR_STORE_APIKEY || '',\n    vector_size: Number(process.env.VECTOR_SIZE_HYBRID) || 1536,\n    index_name: process.env.INDEX_NAME_HYBRID || process.env.PINECONE_INDEX_HYBRID || 'llm-sample-hybrid-index',\n    host: process.env.VECTOR_STORE_HOST,\n    port: process.env.VECTOR_STORE_PORT ? Number(process.env.VECTOR_STORE_PORT) : undefined,\n    deployment: process.env.VECTOR_STORE_DEPLOYMENT\n  }"
  },
  {
    "path": "config/kb/engine.js",
    "content": "module.exports = {\n    name: process.env.VECTOR_STORE_NAME || 'pinecone',\n    type: process.env.INDEX_TYPE || process.env.PINECONE_TYPE || 'serverless',\n    apikey: process.env.VECTOR_STORE_APIKEY || '',\n    vector_size: Number(process.env.VECTOR_SIZE) || 1536,\n    index_name: process.env.INDEX_NAME || process.env.PINECONE_INDEX || 'llm-sample-index',\n    host: process.env.VECTOR_STORE_HOST,\n    port: process.env.VECTOR_STORE_PORT ? Number(process.env.VECTOR_STORE_PORT) : undefined,\n    deployment: process.env.VECTOR_STORE_DEPLOYMENT\n  }"
  },
  {
    "path": "config/kb/prompt/rag/PromptManager.js",
    "content": "const fs = require('fs');\nconst path = require('path');\n\nconst modelMap = {\n    \"gpt-3.5-turbo\":        \"gpt-3.5.txt\",\n    \"gpt-4\":                \"gpt-4.txt\",\n    \"gpt-4-turbo-preview\":  \"gpt-4.txt\",\n    \"gpt-4o\":               \"gpt-4o.txt\",\n    \"gpt-4o-mini\":          \"gpt-4o.txt\",\n    \"gpt-4.1\":              \"gpt-4.1.txt\",\n    \"gpt-4.1-mini\":         \"gpt-4.1.txt\",\n    \"gpt-4.1-nano\":         \"gpt-4.1.txt\",\n    \"gpt-5\":                \"gpt-5.txt\",\n    \"gpt-5-mini\":           \"gpt-5.txt\",\n    \"gpt-5-nano\":           \"gpt-5.txt\",\n    \"gpt-5.1\":              \"gpt-5.x.txt\",\n    \"gpt-5.2\":              \"gpt-5.x.txt\",\n    \"gpt-5.3-chat-latest\":  \"gpt-5.x.txt\",\n    \"gpt-5.4\":              \"gpt-5.x.txt\",\n    \"gpt-5.4-mini\":         \"gpt-5.x.txt\",\n    \"gpt-5.4-nano\":         \"gpt-5.x.txt\",\n    \"general\":              \"general.txt\"\n}\n\n\nclass PromptManager {\n  \n    constructor(basePath) {\n    this.basePath = basePath;\n    this.cache = new Map();\n  }\n\n  getPrompt(name) {\n    if (this.cache.has(name)) {\n      return this.cache.get(name);\n    }\n\n    const fileName = modelMap[name] || modelMap[\"general\"];\n    const filePath = path.join(this.basePath, fileName);\n\n    let content;\n    try {\n      content = fs.readFileSync(filePath, 'utf-8');\n    } catch (err) {\n      content = fs.readFileSync(\n        path.join(this.basePath, modelMap[\"general\"]),\n        'utf-8'\n      );\n    }\n\n    this.cache.set(name, content);\n    return content;\n  }\n}\n\nPromptManager.modelMap = modelMap;\nmodule.exports = PromptManager;"
  },
  {
    "path": "config/kb/prompt/rag/general.txt",
    "content": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context and the chat history to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer.\n\n==Retrieved context start==\n{context}\n==Retrieved context end=="
  },
  {
    "path": "config/kb/prompt/rag/gpt-3.5.txt",
    "content": "You are an helpful assistant for question-answering tasks.\n\nUse ONLY the pieces of retrieved context delimited by #### and the chat history to answer the question.\n\nIf you don't know the answer, just say: \"I don't know<NOANS>\"\n\n####\n{context}\n####"
  },
  {
    "path": "config/kb/prompt/rag/gpt-4.1.txt",
    "content": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context and the chat history to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, append <NOANS> at the end of the answer\n\n==Retrieved context start==\n{context}\n==Retrieved context end=="
  },
  {
    "path": "config/kb/prompt/rag/gpt-4.txt",
    "content": "You are an helpful assistant for question-answering tasks.\n\nUse ONLY the pieces of retrieved context delimited by #### and the chat history to answer the question.\n\nIf you don't know the answer, just say that you don't know.\n\nIf and only if none of the retrieved context is useful for your task, add this word to the end <NOANS>\n\n####\n{context}\n####"
  },
  {
    "path": "config/kb/prompt/rag/gpt-4o.txt",
    "content": "You are an helpful assistant for question-answering tasks. Follow these steps carefully:\n\n1. Answer in the same language of the user question, regardless of the retrieved context language\n2. Use ONLY the pieces of the retrieved context and the chat history to answer the question.\n3. If the retrieved context does not contain sufficient information to generate an accurate and informative answer, return <NOANS>.\n\n==Retrieved context start==\n{context}\n==Retrieved context end=="
  },
  {
    "path": "config/kb/prompt/rag/gpt-5.txt",
    "content": "# ROLE\nYou are an AI assistant that answers the user's question using only the information contained in the provided context.\n\n# LANGUAGE\nAnswer in the same language as the user's question.\n\n# CONTEXT\nYou will receive a context delimited by ######:\n######\n{context}\n######\n\n# INSTRUCTIONS\n- Use only the information explicitly contained in the context.\n- Answer the user's question directly, as a human assistant would.\n- Do not mention the context, the document, the source, or the fact that information was provided.\n- Do not say phrases such as:\n  - \"according to the context\"\n  - \"in the provided context\"\n  - \"the document says\"\n  - \"based on the information provided\"\n- Do not explain your reasoning.\n- Do not repeat the question.\n- Keep the answer concise, clear, and natural.\n- Do not add assumptions, external knowledge, or details not supported by the context.\n\n# FALLBACK\nIf the context does not contain enough information to answer the question, reply with exactly:\n<NOANS>\n\n# OUTPUT\nReturn only the final answer, with no preamble and no meta-commentary."
  },
  {
    "path": "config/kb/prompt/rag/gpt-5.x.txt",
    "content": "# ROLE\nYou are an AI assistant that answers the user's question using only the information contained in the provided context.\n\n# LANGUAGE\nAnswer in the same language as the user's question.\n\n# CONTEXT\nYou will receive a context delimited by ######:\n######\n{context}\n######\n\n# INSTRUCTIONS\n- Use only the information explicitly contained in the context.\n- Answer the user's question directly, as a human assistant would.\n- Do not mention the context, the document, the source, or the fact that information was provided.\n- Do not say phrases such as:\n  - \"according to the context\"\n  - \"in the provided context\"\n  - \"the document says\"\n  - \"based on the information provided\"\n- Do not explain your reasoning.\n- Do not repeat the question.\n- Keep the answer concise, clear, and natural.\n- Do not add assumptions, external knowledge, or details not supported by the context.\n\n# FALLBACK\nIf the context does not contain enough information to answer the question, reply with exactly:\n<NOANS>\n\n# OUTPUT\nReturn only the final answer, with no preamble and no meta-commentary."
  },
  {
    "path": "config/kb/situatedContext.js",
    "content": "module.exports = {\n    enable: process.env.SITUATED_CONTEXT_ENABLE === \"true\",\n    provider: process.env.SITUATED_CONTEXT_PROVIDER || \"openai\",\n    model: process.env.SITUATED_CONTEXT_MODEL || \"gpt-4o\",\n    api_key: \"\"\n}"
  },
  {
    "path": "config/labels/widget.json",
    "content": "[\n    {\n        \"lang\": \"EN\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\": \"type your message..\",\n            \"LABEL_START_NW_CONV\": \"New conversation\",\n            \"LABEL_FIRST_MSG\": \"Describe shortly your problem, you will be contacted by an agent.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔  All operators are offline at the moment. You can anyway describe your problem. It will be assigned to the support team who will get back to you as soon as possible.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔  Our offices are closed. You can anyway describe your problem. It will be assigned to the support team who will get back to you as soon as possible.\",\n            \"LABEL_WHATSAPP\": \"Message us\",\n            \"LABEL_SELECT_TOPIC\": \"Select a topic\",\n            \"LABEL_COMPLETE_FORM\": \"Complete the form to start a conversation with the next available agent.\",\n            \"LABEL_FIELD_NAME\": \"Name\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Required field (minimum 5 characters).\",\n            \"LABEL_FIELD_EMAIL\": \"Email\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Enter a valid email address.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Required field\",\n            \"LABEL_WRITING\": \"is writing...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Send a new message\",\n            \"AGENT_NOT_AVAILABLE\": \" Offline\",\n            \"AGENT_AVAILABLE\": \" Online\",\n            \"GUEST_LABEL\": \"Guest\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"All operators are offline at the moment\",\n            \"LABEL_LOADING\": \"Loading...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Need Help?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Click here and start chatting with us!\",\n            \"CUSTOMER_SATISFACTION\": \"Customer satisfaction\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Your opinion on our customer service\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Download transcript\",\n            \"BACK\": \"Back\",\n            \"CONTINUE\": \"Continue\",\n            \"YOUR_RATING\": \"your rating\",\n            \"WRITE_YOUR_OPINION\": \"Write your opinion ... (optional)\",\n            \"SUBMIT\": \"Submit\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Thank you for your evaluation\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"your rating has been received\",\n            \"ALERT_LEAVE_CHAT\": \"Do you want to leave the chat?\",\n            \"YES\": \"Yes\",\n            \"NO\": \"No\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Minimize chat\",\n            \"BUTTON_EDIT_PROFILE\": \"Update profile\",\n            \"RATE_CHAT\": \"Rate chat\",\n            \"WELCOME_TITLE\": \"Hi, welcome to Tiledesk 👋\",\n            \"WELCOME_MSG\": \"How can we help?\",\n            \"WELCOME\": \"Welcome\",\n            \"OPTIONS\": \"options\",\n            \"SOUND_OFF\": \"Sound off\",\n            \"SOUND_ON\": \"Sound on\",\n            \"LOGOUT\": \"Logout\",\n            \"CLOSE\": \"Close\",\n            \"RESTART\":\"Restart\",\n            \"PREV_CONVERSATIONS\": \"Your conversations\",\n            \"YOU\": \"You\",\n            \"SHOW_ALL_CONV\": \"show all\",\n            \"START_A_CONVERSATION\": \"Start a conversation\",\n            \"NO_CONVERSATION\": \"No conversation\",\n            \"SEE_PREVIOUS\": \"see previous\",\n            \"WAITING_TIME_FOUND\": \"The team typically replies in $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"The team will reply as soon as possible\",\n            \"CLOSED\": \"Closed\",\n            \"CLOSE_CHAT\": \"Close chat\",\n            \"MINIMIZE\":\"Minimize\",\n            \"MAXIMIZE\":\"Maximize\",\n            \"CONFIRM_CLOSE_CHAT\":\"Are you sure you wanna close this chat?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\":\"you\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\":\"you have been added \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\":\"the chat\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\":\"joined\",\n            \"INFO_SUPPORT_CHAT_REOPENED\":\"Chat reopened\",\n            \"INFO_SUPPORT_CHAT_CLOSED\":\"Chat closed\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Lead updated\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"removed from group\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"has left the conversation\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"A new support request has been assigned to you\",\n\n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Full name\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Email\", \n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\":  \"Invalid email address\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Phone\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Phone is required\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Your message for the support team\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Before proceeding in the conversation please agree to our <a href='https://tiledesk.com/termsofservice/' target='_blank'>Terms</a> and <a href='https://tiledesk.com/privacy.html' target='_blank'>Privacy Policy</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"I agree\",\n            \"PRECHAT_REQUIRED_ERROR\": \"This field is required\",\n            \n            \"TICKET_TAKING\":\"The request has been received and the assistance staff is dealing with it.\\nTo add more comments, reply to this email.\",\n\n            \"LABEL_TODAY\": \"today\",\n            \"LABEL_TOMORROW\": \"yesterday\",\n            \"LABEL_LAST_ACCESS\": \"last access\",\n            \"LABEL_TO\": \"at\",\n            \"ARRAY_DAYS\": [\"Monday\", \"Tuesday\", \"Wednesday\", \"Thursday\", \"Friday\", \"Saturday\", \"Sunday\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"sent an attachment\",\n            \"SENT_AN_IMAGE\": \"sent an image\",\n    \n            \"LABEL_PREVIEW\": \"Preview\",\n            \"SWITCH_TO\":\"Or switch to:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Max allowed size {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"The file exceeds the maximum allowed size\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\": \"IT\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\": \"Scrivi la tua domanda...\",\n            \"LABEL_START_NW_CONV\": \"Nuova conversazione\",\n            \"LABEL_FIRST_MSG\": \"Descrivi sinteticamente il tuo problema, ti metteremo in contatto con un operatore specializzato.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Tutti gli operatori sono offline al momento.Puoi comunque descrivere il tuo problema. Sarà assegnato al team di supporto che ti risponderà appena possibile.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 I nostri uffici sono chiusi. Puoi comunque descrivere il tuo problema. Sarà assegnato al team di supporto che ti risponderà appena possibile.\",\n            \"LABEL_WHATSAPP\": \"Contattaci\",\n            \"LABEL_SELECT_TOPIC\": \"Seleziona un argomento\",\n            \"LABEL_COMPLETE_FORM\": \"Completa il form per iniziare una conversazione con il prossimo agente disponibile.\",\n            \"LABEL_FIELD_NAME\": \"Nome\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Nome richiesto (minimo 2 caratteri).\",\n            \"LABEL_FIELD_EMAIL\": \"Email\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Inserisci un indirizzo email valido.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Campo richiesto\",\n            \"LABEL_WRITING\": \"sta scrivendo...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Invia un nuovo messaggio\",\n            \"AGENT_NOT_AVAILABLE\": \" Offline\",\n            \"AGENT_AVAILABLE\": \" Online\",\n            \"GUEST_LABEL\": \"Ospite\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Tutti gli operatori sono offline al momento\",\n            \"LABEL_LOADING\": \"Caricamento...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Bisogno di aiuto?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Clicca qui e inizia a chattare con noi!\",\n            \"CUSTOMER_SATISFACTION\": \"Valutazione servizio\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"il tuo giudizio sul nostro servizio clienti\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Scarica transcript\",\n            \"BACK\": \"Indietro\",\n            \"CONTINUE\":\"Continua\",\n            \"YOUR_RATING\": \"il tuo voto\",\n            \"WRITE_YOUR_OPINION\": \"Scrivi la tua opinione...(opzionale)\",\n            \"SUBMIT\": \"Invia\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Grazie per la tua valutazione\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"il tuo voto è stato ricevuto\",\n            \"ALERT_LEAVE_CHAT\": \"Vuoi abbandonare la conversazione?\",\n            \"YES\": \"Si\",\n            \"NO\": \"No\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Riduci a icona la chat\",\n            \"BUTTON_EDIT_PROFILE\": \"Modifica profilo\",\n            \"RATE_CHAT\": \"Valuta chat\",\n            \"WELCOME_TITLE\": \"Ciao, benvenuto su Tiledesk 👋\",\n            \"WELCOME_MSG\": \"Come possiamo aiutarti?\",\n            \"WELCOME\": \"Benvenuto\",\n            \"OPTIONS\": \"opzioni\",\n            \"SOUND_OFF\": \"Suono spento\",\n            \"SOUND_ON\": \"Suono acceso\",\n            \"LOGOUT\": \"Disconnetti\",\n            \"CLOSE\": \"Chiudi\",\n            \"RESTART\":\"Ricomincia\",\n            \"PREV_CONVERSATIONS\": \"Le tue conversazioni\",\n            \"YOU\": \"Tu\",\n            \"SHOW_ALL_CONV\": \"vedi tutte\",\n            \"START_A_CONVERSATION\": \"Inizia una conversazione\",\n            \"NO_CONVERSATION\": \"Nessuna conversazione attiva\",\n            \"SEE_PREVIOUS\": \"vedi precedenti\",\n            \"WAITING_TIME_FOUND\": \"Il team tipicamente risponde in $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"Vi risponderemo appena possibile\",\n            \"CLOSED\": \"Chiusa\",\n            \"CLOSE_CHAT\": \"Chiudi chat\",\n            \"MINIMIZE\":\"Minimizza\",\n            \"MAXIMIZE\":\"Massimizza\",\n            \"CONFIRM_CLOSE_CHAT\":\"Vuoi davvero chiudere questa chat?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\":\"tu\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\":\"sei stato aggiunto \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\":\"si è unito\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\":\"alla chat\",\n            \"INFO_SUPPORT_CHAT_REOPENED\":\"Chat riaperta\",\n            \"INFO_SUPPORT_CHAT_CLOSED\":\"Chat chiusa\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Contatto aggiornato\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"ha lasciato il gruppo\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\": \"ha abbandonato la conversazione\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Una nuova richiesta di supporto è stata assegnata a te\",\n\n\t\t\t\"TICKET_TAKING\":\"La richiesta è stata ricevuta e il personale di assistenza se ne sta occupando.\\nPer aggiungere ulteriori commenti, rispondi a questa email.\",\n\n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Nome completo\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-mail\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Indirizzo email non valido\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefono\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefono richiesto\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Il tuo messaggio per il team di supporto\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Prima di procedere nella conversazione, accetta i nostri <a href='https://tiledesk.com/termsofservice/' target='_blank'>Termini</a> e <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Norme sulla privacy</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Sono d'accordo\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Questo campo è obbligatorio\",\n\n            \"LABEL_TODAY\": \"oggi\",\n            \"LABEL_TOMORROW\": \"ieri\",\n            \"LABEL_LAST_ACCESS\": \"ultimo accesso\",\n            \"LABEL_TO\": \"a\",\n            \"ARRAY_DAYS\": [\"Lunedì\", \"Martedì\", \"Mercoledì\", \"Giovedì\", \"Venerdì\", \"Sabato\", \"Domenica\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"ha inviato un allegato\",\n            \"SENT_AN_IMAGE\":\"ha inviato un'immagine\",\n            \n            \"LABEL_PREVIEW\": \"Anteprima\",\n            \"SWITCH_TO\":\"Oppure passa a:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Dimensione massima consentita {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Il file supera la dimensione massima consentita\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\": \"FR\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\": \"Écrivez votre question ...\",\n            \"LABEL_START_NW_CONV\": \"Nouvelle conversation\",\n            \"LABEL_FIRST_MSG\": \"Décrivez brièvement votre problème, nous vous mettrons en relation avec un opérateur spécialisé.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Tous les opérateurs sont actuellement hors ligne. Vous pouvez toujours décrire votre problème. Il sera attribué à l'équipe d'assistance qui vous répondra dans les plus brefs délais.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Nos bureaux sont fermés. Vous pouvez toujours décrire votre problème. Il sera attribué à l'équipe d'assistance qui vous répondra dans les plus brefs délais.\",\n            \"LABEL_WHATSAPP\": \"Contactez-nous\",\n            \"LABEL_SELECT_TOPIC\": \"Sélectionnez un sujet\",\n            \"LABEL_COMPLETE_FORM\": \"Remplissez le formulaire pour démarrer une conversation avec le prochain agent disponible.\",\n            \"LABEL_FIELD_NAME\": \"Prenom\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Nom demandé (minimum 2 caractères).\",\n            \"LABEL_FIELD_EMAIL\": \"Email\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Entrer une adresse email valide.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Champ obligatoire\",\n            \"LABEL_WRITING\": \"écrit ...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Envoyez un nouveau message\",\n            \"AGENT_NOT_AVAILABLE\": \" Hors ligne\",\n            \"AGENT_AVAILABLE\": \" En ligne\",\n            \"GUEST_LABEL\": \"Hôte\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Tous les opérateurs sont actuellement hors ligne\",\n            \"LABEL_LOADING\": \"Chargement en cours ...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Besoin d'aide?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Cliquez ici et commencez à discuter avec nous!\",\n            \"CUSTOMER_SATISFACTION\": \"Évaluation des services\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"votre avis sur notre service client\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Télécharger la transcription\",\n            \"BACK\": \"En arrière\",\n            \"CONTINUE\": \"Continue\",\n            \"YOUR_RATING\": \"votre vote\",\n            \"WRITE_YOUR_OPINION\": \"Écrivez votre opinion ... (facultatif)\",\n            \"SUBMIT\": \"Soumettre\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Merci pour votre évaluation\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"votre vote a été reçu\",\n            \"ALERT_LEAVE_CHAT\": \"Voulez-vous quitter la conversation?\",\n            \"YES\": \"Oui\",\n            \"NO\": \"Non\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Minimisez le chat\",\n            \"BUTTON_EDIT_PROFILE\": \"Modifier le profil\",\n            \"RATE_CHAT\": \"évaluer la conversation\",\n            \"WELCOME_TITLE\": \"Salut, bienvenue à Tiledesk 👋\",\n            \"WELCOME_MSG\": \"Comment pouvons-nous vous aider?\",\n            \"WELCOME\": \"Bienvenue\",\n            \"OPTIONS\": \"options\",\n            \"SOUND_OFF\": \"Sound off\",\n            \"SOUND_ON\": \"Sound on\",\n            \"LOGOUT\": \"Connectez-out\",\n            \"CLOSE\": \"Fermer\",\n            \"RESTART\":\"Redémarrer\",\n            \"PREV_CONVERSATIONS\": \"Vos conversations\",\n            \"YOU\": \"Toi\",\n            \"SHOW_ALL_CONV\": \"voir tout\",\n            \"START_A_CONVERSATION\": \"Démarrer une conversation\",\n            \"NO_CONVERSATION\": \"Aucune conversation active\",\n            \"SEE_PREVIOUS\": \"voir les conversations précédentes\",\n            \"WAITING_TIME_FOUND\": \"L'équipe répond généralement en $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"Nous vous répondrons dans les plus brefs délais\",\n            \"CLOSED\": \"Fermé\",\n            \"CLOSE_CHAT\": \"Fermer le chat\",\n            \"MINIMIZE\":\"Minimiser\",\n            \"MAXIMIZE\":\"Maximiser\",\n            \"CONFIRM_CLOSE_CHAT\":\"Etes-vous sûr de vouloir fermer cette discussion?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\":\"toi\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\":\"ont été ajoutés à \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\":\"rejointe\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\":\"la discussion\",\n            \"INFO_SUPPORT_CHAT_REOPENED\":\"Chat rouvert\",\n            \"INFO_SUPPORT_CHAT_CLOSED\":\"Chat fermé\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Prospect mis à jour\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"supprimé du groupe\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"a quitté la conversation\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Une nouvelle demande de support vous a été attribuée\",\n\n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Nom complet\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-mail\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Adresse e-mail invalide\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Téléphone\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Le téléphone est requis\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Votre message pour l'équipe d'assistance\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Avant de poursuivre la conversation, veuillez accepter nos <a href='https://tiledesk.com/termsofservice/' target='_blank'>Conditions</a> et <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Politique de confidentialité</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Je suis d'accord\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Ce champ est obligatoire\",\n\n\t\t\t\"TICKET_TAKING\":\"La demande a été reçue et le personnel d'assistance la traite.\\nPour ajouter d'autres commentaires, répondez à cet e-mail.\",\n\t\t\t\n            \"LABEL_TODAY\": \"aujourd'hui\",\n            \"LABEL_TOMORROW\": \"hier\",\n            \"LABEL_LAST_ACCESS\": \"dernier accès\",\n            \"LABEL_TO\": \"à\",\n            \"ARRAY_DAYS\": [\"Lundi\", \"Mardi\", \"Mercredi\", \"Jeudi\", \"Vendredi\", \"Samedi\", \"Dimanche\"],\n\n            \"SENT_AN_ATTACHMENT\": \"envoyé une pièce jointe\",\n            \"SENT_AN_IMAGE\": \"envoyé une image\",\n\n            \"LABEL_PREVIEW\": \"Aperçu\",\n            \"SWITCH_TO\":\"Ou passer à:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Taille maximale autorisée {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Le fichier dépasse la taille maximale autorisée\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n     },\n     {\n        \"lang\": \"ES\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\": \"escribe tu mensaje..\",\n            \"LABEL_START_NW_CONV\": \"Nueva conversación\",\n            \"LABEL_FIRST_MSG\":\"Describa brevemente su problema, un agente lo contactará.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔 Todos los operadores están desconectados en este momento. En todo caso, puedes describir tu problema que se asignará al equipo de soporte que le responderá lo antes posible.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔 Nuestras oficinas están cerradas. En todo caso, puedes describir tu problema que se asignará al equipo de soporte que le responderá lo antes posible.\",\n            \"LABEL_WHATSAPP\": \"Envíanos un mensaje\",\n            \"LABEL_SELECT_TOPIC\": \"Elige un tema\",\n            \"LABEL_COMPLETE_FORM\": \"Complete el formulario para iniciar una conversación con el próximo agente disponible.\",\n            \"LABEL_FIELD_NAME\": \"Nombre\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Campo obligatorio (mínimo 5 caracteres).\",\n            \"LABEL_FIELD_EMAIL\": \"Email\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Introduzca una dirección de correo electrónico válida.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Campo obligatorio\",\n            \"LABEL_WRITING\": \"esta escribiendo...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Enviar un mensaje nuevo\",\n            \"AGENT_NOT_AVAILABLE\": \" Fuera de línea\",\n            \"AGENT_AVAILABLE\": \" En línea\",\n            \"GUEST_LABEL\": \"Huésped\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Todos los operadores están desconectados en este momento.\",\n            \"LABEL_LOADING\": \"Cargando...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 ¿Necesitas ayuda?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"¡Haz clic aquí y comienza a chatear con nosotros!\",\n            \"CUSTOMER_SATISFACTION\": \"La satisfacción del cliente\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"su opinión sobre nuestro servicio al cliente\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Descargar transcripción\",\n            \"BACK\": \"Atrás\",\n            \"CONTINUE\": \"Continuar\",\n            \"YOUR_RATING\": \"tu clasificación\",\n            \"WRITE_YOUR_OPINION\": \"Escribe tu opinión ... (opcional)\",\n            \"SUBMIT\": \"Enviar\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Gracias por tu evaluación\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"su voto ha sido recibido\",\n            \"ALERT_LEAVE_CHAT\": \"quieres salir del chat?\",\n            \"YES\": \"Si\",\n            \"NO\": \"No\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Minimiza el chat\",\n            \"BUTTON_EDIT_PROFILE\": \"Actualización del perfil\",\n            \"RATE_CHAT\": \"Califica el chat\",\n            \"WELCOME_TITLE\": \"Hola, bienvenido a Tiledesk 👋\",\n            \"WELCOME_MSG\": \"¿Cómo podemos ayudar?\",\n            \"WELCOME\": \"Podemos\",\n            \"OPTIONS\": \"opciones\",\n            \"SOUND_OFF\": \"Sonido apagado\",\n            \"SOUND_ON\": \"Sonido encendido\",\n            \"LOGOUT\": \"Cerrar sesión\",\n            \"CLOSE\": \"cerca\",\n            \"RESTART\":\"Reiniciar\",\n            \"PREV_CONVERSATIONS\": \"Tus conversaciones\",\n            \"YOU\": \"Tú\",\n            \"SHOW_ALL_CONV\": \"mostrar todo\",\n            \"START_A_CONVERSATION\": \"Iniciar una conversación\",\n            \"NO_CONVERSATION\": \"Sin conversación\",\n            \"SEE_PREVIOUS\": \"ver anterior\",\n            \"WAITING_TIME_FOUND\": \"El equipo típicamente responde en $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"El equipo responderá lo antes posible\",\n            \"CLOSED\": \"CERRADA\",\n            \"CLOSE_CHAT\": \"Cerrar chat\",\n            \"MINIMIZE\":\"Minimizar\",\n            \"MAXIMIZE\":\"Maximizar\",\n            \"CONFIRM_CLOSE_CHAT\":\"¿Estás segura de que quieres cerrar este chat?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\":\"tú\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\":\"han sido agregados a \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\":\"joined\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\":\"el chat\",\n            \"INFO_SUPPORT_CHAT_REOPENED\":\"Chat reabierto\",\n            \"INFO_SUPPORT_CHAT_CLOSED\":\"Chat cerrada\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Cliente potencial actualizado\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"eliminado del grupo\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"abandonó la conversación\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Se le ha asignado una nueva solicitud de soporte\",\n    \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Nombre completo\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Correo electrónico\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Dirección de correo electrónico no válida\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Teléfono\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Se requiere teléfono\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Tu mensaje para el equipo de soporte\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Antes de continuar con la conversación, acepte nuestros <a href='https://tiledesk.com/termsofservice/' target='_blank'>Términos</a> y <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Política de privacidad</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Acepto\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Este campo es obligatorio\",\n            \n            \"TICKET_TAKING\":\"La solicitud ha sido recibida y el personal de asistencia está tratando con ella. \\nPara agregar más comentarios, responda a este correo electrónico.\",\n            \n            \"LABEL_TODAY\": \"hoy\",\n            \"LABEL_TOMORROW\": \"ayer\",\n            \"LABEL_LAST_ACCESS\": \"ultimo acceso\",\n            \"LABEL_TO\": \"a\",\n            \"ARRAY_DAYS\": [\"Lunes\", \"Martes\", \"Miércoles\", \"Jueves\", \"Viernes\", \"Sábado\", \"Domingo\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"envió un archivo adjunto\",\n            \"SENT_AN_IMAGE\": \"envió una imagen\",\n\n            \"LABEL_PREVIEW\": \"Avance\",\n            \"SWITCH_TO\":\"O cambiar a:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Tamaño máximo permitido {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"El archivo supera el tamaño máximo permitido\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\":\"DE\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\":\"Geben Sie Ihre Nachricht ein ...\",\n            \"LABEL_START_NW_CONV\":\"Neues Gespräch\",\n            \"LABEL_FIRST_MSG\":\"Beschreiben Sie in Kürze Ihr Problem. Sie werden von einem Agenten kontaktiert.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔 Alle Betreiber sind derzeit offline. Sie können Ihr Problem trotzdem beschreiben. Es wird dem Support-Team zugewiesen, das Ihnen so schnell wie möglich antwortet.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔 Unsere Büros sind geschlossen. Sie können Ihr Problem trotzdem beschreiben. Es wird dem Support-Team zugewiesen, das Ihnen so schnell wie möglich antwortet.\",\n            \"LABEL_WHATSAPP\": \"Nachricht an uns\",\n            \"LABEL_SELECT_TOPIC\":\"Wähle ein Thema\",\n            \"LABEL_COMPLETE_FORM\":\"Füllen Sie das Formular aus, um ein Gespräch mit dem nächsten verfügbaren Agenten zu beginnen.\",\n            \"LABEL_FIELD_NAME\":\"Name\",\n            \"LABEL_ERROR_FIELD_NAME\":\"Erforderliches Feld (mindestens 5 Zeichen).\",\n            \"LABEL_FIELD_EMAIL\":\"Email\",\n            \"LABEL_ERROR_FIELD_EMAIL\":\"Geben sie eine gültige E-Mail-Adresse an.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Pflichtfeld\",\n            \"LABEL_WRITING\":\"schreibt...\",\n            \"LABEL_SEND_NEW_MESSAGE\":\"Senden Sie eine neue Nachricht\",\n            \"AGENT_NOT_AVAILABLE\":\" Offline\",\n            \"AGENT_AVAILABLE\":\" Online\",\n            \"GUEST_LABEL\":\"Gast\",\n            \"ALL_AGENTS_OFFLINE_LABEL\":\"Alle Betreiber sind derzeit offline\",\n            \"LABEL_LOADING\":\"Wird geladen...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\":\"🖐 Brauchen Sie Hilfe?\",\n            \"CALLOUT_MSG_PLACEHOLDER\":\"Klicken Sie hier und beginnen Sie mit uns zu chatten!\",\n            \"CUSTOMER_SATISFACTION\":\"Kundenzufriedenheit\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\":\"Ihre Meinung zu unserem Kundenservice\",\n            \"DOWNLOAD_TRANSCRIPT\":\"Transkript herunterladen\",\n            \"BACK\":\"Zurück\",\n            \"CONTINUE\": \"Fortsetzung\",\n            \"YOUR_RATING\":\"Deine Bewertung\",\n            \"WRITE_YOUR_OPINION\":\"Schreiben Sie Ihre Meinung ... (optional)\",\n            \"SUBMIT\":\"Einreichen\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\":\"Vielen Dank für Ihre Bewertung\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\":\"Ihre Bewertung wurde erhalten\",\n            \"ALERT_LEAVE_CHAT\":\"Möchten Sie den Chat verlassen?\",\n            \"YES\":\"Ja\",\n            \"NO\":\"Nein\",\n            \"BUTTON_CLOSE_TO_ICON\":\"Chat minimieren\",\n            \"BUTTON_EDIT_PROFILE\":\"Profil aktualisieren\",\n            \"RATE_CHAT\":\"Chat bewerten\",\n            \"WELCOME_TITLE\":\"Hallo, willkommen in Tiledesk 👋\",\n            \"WELCOME_MSG\":\"Wie können wir helfen?\",\n            \"WELCOME\":\"Herzlich willkommen\",\n            \"OPTIONS\":\"Optionen\",\n            \"SOUND_OFF\":\"Ton aus\",\n            \"SOUND_ON\":\"Ton an\",\n            \"LOGOUT\":\"Ausloggen\",\n            \"CLOSE\":\"Schließen\",\n            \"RESTART\":\"Neu starten\",\n            \"PREV_CONVERSATIONS\":\"Ihre Gespräche\",\n            \"YOU\":\"Du\",\n            \"SHOW_ALL_CONV\":\"zeige alles\",\n            \"START_A_CONVERSATION\":\"Eine Konversation beginnen\",\n            \"NO_CONVERSATION\":\"keine Gespräche\",\n            \"SEE_PREVIOUS\":\"siehe vorher\",\n            \"WAITING_TIME_FOUND\":\"Das Team antwortet normalerweise in $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\":\"Das Team wird so schnell wie möglich antworten\",\n            \"CLOSED\":\"Geschlossen\",\n            \"CLOSE_CHAT\": \"Chat schließen\",\n            \"MINIMIZE\":\"Minimieren\",\n            \"MAXIMIZE\":\"Maximieren\",\n            \"CONFIRM_CLOSE_CHAT\":\"Möchten Sie diesen Chat wirklich schließen?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"Sie\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"Sie wurden hinzugefügt \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"zum Chatten\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"beigetreten\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Chat wieder geöffnet\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Chat geschlossen\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Leitung aktualisiert\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"aus der Gruppe entfernt\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"hat die Unterhaltung verlassen\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Ihnen wurde eine neue Support-Anfrage zugewiesen\",\n           \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Vollständiger Name\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-Mail\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Ungültige E-Mail-Adresse\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefon\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefon ist erforderlich\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Ihre Nachricht an das Support-Team\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Bevor Sie mit dem Gespräch fortfahren, stimmen Sie bitte unseren <a href='https://tiledesk.com/termsofservice/' target='_blank'>Bedingungen</a> und <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Datenschutzerklärung</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Ich stimme zu\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Dieses Feld ist erforderlich\",\n        \n       \t \t\"TICKET_TAKING\":\"Die Anfrage ist eingegangen und wird vom Hilfspersonal bearbeitet.\\nUm weitere Kommentare hinzuzufügen, antworten Sie auf diese E-Mail.\",\n            \n            \"LABEL_TODAY\": \"heute\",\n            \"LABEL_TOMORROW\": \"gestern\",\n            \"LABEL_LAST_ACCESS\": \"letzter Zugriff\",\n            \"LABEL_TO\": \"bei\",\n            \"ARRAY_DAYS\": [\"Montag\", \"Dienstag\", \"Mittwoch\", \"Donnerstag\", \"Freitag\", \"Samstag\", \"Sonntag\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"Anhang gesendet\",\n            \"SENT_AN_IMAGE\": \"ein Bild gesendet\",\n    \n            \"LABEL_PREVIEW\": \"Vorschau\",\n            \"SWITCH_TO\":\"Oder wechseln Sie zu:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Maximal zulässige Größe {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Die Datei überschreitet die maximal zulässige Größe\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\":\"PT\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\":\"Digite sua mensagem..\",\n            \"LABEL_START_NW_CONV\":\"Nova conversa\",\n            \"LABEL_FIRST_MSG\":\"Descreva em breve o seu problema, você será contactado por um agente.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔 Todos os operadores estão offline no momento. Em qualquer caso, você pode descrever seu problema. Ele será atribuído à equipe de suporte que responderá o mais breve possível.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔 Nossos escritórios estão fechados. Em qualquer caso, você pode descrever seu problema. Ele será atribuído à equipe de suporte que responderá o mais breve possível.\",\n            \"LABEL_WHATSAPP\": \"Envie-nos uma mensagem\",\n            \"LABEL_SELECT_TOPIC\":\"Selecione um topico\",\n            \"LABEL_COMPLETE_FORM\":\"Preencha o formulário para iniciar uma conversa com o próximo agente disponível.\",\n            \"LABEL_FIELD_NAME\":\"Nome\",\n            \"LABEL_ERROR_FIELD_NAME\":\"Campo obrigatório (mínimo de 2 caracteres).\",\n            \"LABEL_FIELD_EMAIL\":\"o email\",\n            \"LABEL_ERROR_FIELD_EMAIL\":\"Digite um endereço de e-mail válido.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Campo obrigatório\",\n            \"LABEL_WRITING\":\"está escrevendo...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Envie uma nova mensagem\",\n            \"AGENT_NOT_AVAILABLE\":\"Desligado\",\n            \"AGENT_AVAILABLE\":\" Ligado\",\n            \"GUEST_LABEL\":\"Convidado\",\n            \"ALL_AGENTS_OFFLINE_LABEL\":\"Todos os operadores estão offline no momento\",\n            \"LABEL_LOADING\":\"A carregar...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\":\"🖐 Precisas de ajuda?\",\n            \"CALLOUT_MSG_PLACEHOLDER\":\"Clique aqui e comece a conversar conosco!\",\n            \"CUSTOMER_SATISFACTION\":\"Satisfação do cliente\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\":\"A sua opinião sobre o nosso serviço de cliente\",\n            \"DOWNLOAD_TRANSCRIPT\":\"Descarregar a  transcrição\",\n            \"BACK\":\"Voltar\",\n            \"CONTINUE\": \"Continuar\",\n            \"YOUR_RATING\":\"A sua avaliação\",\n            \"WRITE_YOUR_OPINION\":\"Escreva a sua opinião ... (opcional)\",\n            \"SUBMIT\":\"Enviar\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\":\"Obrigado pela sua avaliação\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\":\"Sua classificação foi recebida\",\n            \"ALERT_LEAVE_CHAT\":\"Deseja sair da conversa?\",\n            \"YES\":\"Sim\",\n            \"NO\":\"Não\",\n            \"BUTTON_CLOSE_TO_ICON\":\"Minimizar messagem\",\n            \"BUTTON_EDIT_PROFILE\":\"Atualizar perfil\",\n            \"RATE_CHAT\":\"Classificar conversa\",\n            \"WELCOME_TITLE\":\"Olá, seja bem-vindo ao Tiledesk 👋\",\n            \"WELCOME_MSG\":\"Como podemos ajudar?\",\n            \"WELCOME\":\"Bem-vinda\",\n            \"OPTIONS\":\"Opções\",\n            \"SOUND_OFF\":\"Som desligado\",\n            \"SOUND_ON\":\"Som ligado\",\n            \"LOGOUT\":\"Sair\",\n            \"CLOSE\":\"Fechar\",\n            \"RESTART\":\"Reiniciar\",\n            \"PREV_CONVERSATIONS\":\"Suas conversas\",\n            \"YOU\":\"Você\",\n            \"SHOW_ALL_CONV\":\"Mostre tudo\",\n            \"START_A_CONVERSATION\":\"Iniciar uma conversa\",\n            \"NO_CONVERSATION\":\"Nenhuma conversa\",\n            \"SEE_PREVIOUS\":\"ver anterior\",\n            \"WAITING_TIME_FOUND\":\"A equipe normalmente responde em $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\":\"A equipe responderá o mais breve possível\",\n            \"CLOSED\":\"Fechado\",\n            \"CLOSE_CHAT\": \"Fechar bate-papo\",\n            \"MINIMIZE\":\"Minimizar\",\n            \"MAXIMIZE\":\"Maximizar\",\n            \"CONFIRM_CLOSE_CHAT\":\"Tem certeza de que deseja fechar este bate-papo?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"você\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"você foi adicionado \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"para conversar\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"aderiu\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Chat reaberto\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Bate-papo fechado\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Lead atualizado\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"removido do grupo\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"deixou a conversa\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Uma nova solicitação de suporte foi atribuída a você\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Nome completo\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-mail\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Endereço de e-mail inválido\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefone\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefone é obrigatório\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Sua mensagem para a equipe de suporte\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Antes de continuar a conversa, concorde com nossos <a href='https://tiledesk.com/termsofservice/' target='_blank'>Termos</a> e <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Política de privacidade</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Eu concordo\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Este campo é obrigatório\",\n        \n        \t\"TICKET_TAKING\":\"A solicitação foi recebida e a equipe de assistência está tratando dela. \\nPara adicionar mais comentários, responda a este e-mail.\",\n        \n            \"LABEL_TODAY\": \"hoje\",\n            \"LABEL_TOMORROW\": \"ontem\",\n            \"LABEL_LAST_ACCESS\": \"último acesso\",\n            \"LABEL_TO\": \"no\",\n            \"ARRAY_DAYS\": [\"Segunda\", \"Terça\", \"Quarta\", \"Quinta\", \"Sexta\", \"Sábado\", \"Domingo\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"enviou um anexo\",\n            \"SENT_AN_IMAGE\": \"enviou uma imagem\",\n    \n            \"LABEL_PREVIEW\": \"Visualizar\",\n            \"SWITCH_TO\":\"Ou mude para:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Tamanho máximo permitido {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"O arquivo excede o tamanho máximo permitido\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\":\"RU\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\":\"введите ваше сообщение ..\",\n            \"LABEL_START_NW_CONV\":\"Новый разговор\",\n            \"LABEL_FIRST_MSG\":\"Опишите вкратце вашу проблему, с вами свяжется агент.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔 Все операторы на данный момент не в сети. В любом случае вы можете описать свою проблему. Он будет назначен в службу поддержки, которая ответит вам как можно скорее.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔 Наши офисы закрыты. В любом случае вы можете описать свою проблему. Он будет назначен в службу поддержки, которая ответит вам как можно скорее.\",\n            \"LABEL_WHATSAPP\": \"Напишите нам\",\n            \"LABEL_SELECT_TOPIC\":\"Выберите тему\",\n            \"LABEL_COMPLETE_FORM\":\"Заполните форму, чтобы начать разговор со следующим доступным агентом.\",\n            \"LABEL_FIELD_NAME\":\"имя\",\n            \"LABEL_ERROR_FIELD_NAME\":\"Обязательное поле (минимум 2 символов).\",\n            \"LABEL_FIELD_EMAIL\":\"Электронное письмо\",\n            \"LABEL_ERROR_FIELD_EMAIL\":\"Введите действительный адрес электронной почты.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Обязательное поле\",\n            \"LABEL_WRITING\":\"пишет...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Отправить новое сообщение\",\n            \"AGENT_NOT_AVAILABLE\":\"Не в сети\",\n            \"AGENT_AVAILABLE\":\"онлайн\",\n            \"GUEST_LABEL\":\"гость\",\n            \"ALL_AGENTS_OFFLINE_LABEL\":\"Все операторы на данный момент не работают\",\n            \"LABEL_LOADING\":\"загрузка...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\":\"🖐 Нужна помощь?\",\n            \"CALLOUT_MSG_PLACEHOLDER\":\"Нажмите здесь и начните общаться с нами!\",\n            \"CUSTOMER_SATISFACTION\":\"Удовлетворенность клиентов\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\":\"Ваше мнение о нашей службе поддержки\",\n            \"DOWNLOAD_TRANSCRIPT\":\"Скачать стенограмму\",\n            \"BACK\":\"назад\",\n            \"CONTINUE\": \"Продолжать\",\n            \"YOUR_RATING\":\"ваш рейтинг\",\n            \"WRITE_YOUR_OPINION\":\"Напишите свое мнение ... (необязательно)\",\n            \"SUBMIT\":\"Разместить\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\":\"Спасибо за вашу оценку\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\":\"Ваша оценка была получена\",\n            \"ALERT_LEAVE_CHAT\":\"Вы хотите выйти из чата?\",\n            \"YES\":\"да\",\n            \"NO\":\"нет\",\n            \"BUTTON_CLOSE_TO_ICON\":\"Свернуть чат\",\n            \"BUTTON_EDIT_PROFILE\":\"Обновить профиль\",\n            \"RATE_CHAT\":\"Оценить чат\",\n            \"WELCOME_TITLE\":\"Привет, добро пожаловать в Tiledesk 👋\",\n            \"WELCOME_MSG\":\"Как мы можем помочь?\",\n            \"WELCOME\":\"желанный\",\n            \"OPTIONS\":\"опции\",\n            \"SOUND_OFF\":\"Звук выключен\",\n            \"SOUND_ON\":\"Звучать на\",\n            \"LOGOUT\":\"Выйти\",\n            \"CLOSE\":\"Закрывать\",\n            \"RESTART\":\"Запустить снова\",\n            \"PREV_CONVERSATIONS\":\"Ваши разговоры\",\n            \"YOU\":\"Вы\",\n            \"SHOW_ALL_CONV\":\"показать все\",\n            \"START_A_CONVERSATION\":\"Начать разговор\",\n            \"NO_CONVERSATION\":\"Нет разговоров\",\n            \"SEE_PREVIOUS\":\"смотри предыдущий\",\n            \"WAITING_TIME_FOUND\":\"Команда обычно отвечает в $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\":\"Команда ответит как можно скорее\",\n            \"CLOSED\":\"Закрыто\",\n            \"CLOSE_CHAT\": \"Закрыть чат\",\n            \"MINIMIZE\":\"Свести к минимуму\",\n            \"MAXIMIZE\":\"Максимизировать\",\n            \"CONFIRM_CLOSE_CHAT\":\"Вы уверены, что хотите закрыть этот чат?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"вы\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"вы добавлены \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"присоединился\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"в чат\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Чат возобновлен\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Чат закрыт\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Интерес обновлен\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"удален из группы\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"вышел из беседы\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Вам назначен новый запрос в службу поддержки\",        \n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Полное имя\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Электронная почта\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Недействительный адрес электронной почты\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Телефон\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Требуется телефон\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Ваше сообщение для службы поддержки\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Прежде чем продолжить разговор, примите наши <a href='https://tiledesk.com/termsofservice/' target='_blank'>Условия</a> и <a href='https:/ /tiledesk.com/privacy.html' target='_blank'>Политика конфиденциальности</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Я согласен\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Это поле обязательно для заполнения\",\n\n\t\t\t\"TICKET_TAKING\":\"Запрос был получен, и обслуживающий персонал занимается им. \\nЧтобы добавить больше комментариев, ответьте на это письмо.\",\n\n            \"LABEL_TODAY\": \"Cегодня\",\n            \"LABEL_TOMORROW\": \"вчерашний день\",\n            \"LABEL_LAST_ACCESS\": \"последний доступ\",\n            \"LABEL_TO\": \"в\",\n            \"ARRAY_DAYS\": [\"Понедельник\", \"Вторник\", \"Среда\", \"Четверг\", \"Пятница\", \"Суббота\", \"Воскресенье\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"отправлено вложение\",\n            \"SENT_AN_IMAGE\": \"отправил изображение\",\n    \n            \"LABEL_PREVIEW\": \"Превью\",\n            \"SWITCH_TO\":\"Или переключитесь на:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Максимально допустимый размер {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Файл превышает максимально допустимый размер\",\n    \t\t\"EMOJI\": \"Эмодзи\"\n        }\n    },\n    {\n        \"lang\":\"TR\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\":\"Mesajınızı yazınız..\",\n            \"LABEL_START_NW_CONV\":\"Yeni konuşma\",\n            \"LABEL_FIRST_MSG\":\"Sorununuzu kısaca açıklayınız, bir temsilci tarafından sizinle iletişime geçilecektir.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔 Şuan tüm operatörler çevrim dışıdır. Yine de sorununuzu tarif edebilirsiniz. Sorununuz size en kısa sürede cevap verebilecek olan destek ekibine iletilecektir.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔 Ofislerimiz kapalıdır. Her halükarda sorununuzu tarif edebilirsiniz. Sorununuz size en kısa sürede cevap verebilecek olan destek ekibine iletilecektir.\",\n            \"LABEL_WHATSAPP\": \"Bize mesaj gönderin\",\n            \"LABEL_SELECT_TOPIC\":\"Bir konu seçiniz\",\n            \"LABEL_COMPLETE_FORM\":\"Sıradaki müsait temsilci ile görüşme başlatmak için formu doldurunuz.\",\n            \"LABEL_FIELD_NAME\":\"İsim\",\n            \"LABEL_ERROR_FIELD_NAME\":\"Gerekli alan (en az 2 karakter).\",\n            \"LABEL_FIELD_EMAIL\":\"Eposta\",\n            \"LABEL_ERROR_FIELD_EMAIL\":\"Geçerli bir eposta adresi giriniz\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Gerekli alan\",\n            \"LABEL_WRITING\":\"Yazıyor…\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Yeni bir mesaj gönder\",\n            \"AGENT_NOT_AVAILABLE\":\"Çevrimdışı\",\n            \"AGENT_AVAILABLE\":\"Çevrimiçi\",\n            \"GUEST_LABEL\":\"Misafir\",\n            \"ALL_AGENTS_OFFLINE_LABEL\":\"Tüm operatörler şuanda çevrimdışıdır\",\n            \"LABEL_LOADING\":\"Yükleniyor…\",\n            \"CALLOUT_TITLE_PLACEHOLDER\":\"🖐 yardıma mı ihtiyacınız var?\",\n            \"CALLOUT_MSG_PLACEHOLDER\":\"Buraya tıklayın ve bizimle sohbet etmeye başlayın!\",\n            \"CUSTOMER_SATISFACTION\":\"Müşteri memnuniyeti\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\":\"Müşteri hizmetlerimizle ilgili görüşleriniz\",\n            \"DOWNLOAD_TRANSCRIPT\":\"Konuşma metnini indir\",\n            \"BACK\":\"Konuşma metnini indir\",\n            \"CONTINUE\": \"Devam etmek\",\n            \"YOUR_RATING\":\"Sizin değerlendirme notunuz\",\n            \"WRITE_YOUR_OPINION\":\"Düşüncenizi yazınız... (isteğe bağlı)\",\n            \"SUBMIT\":\"Gönder\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\":\"Değerlendirmeniz için teşekkür ederiz.\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\":\"Değerlendirme notunuz alındı\",\n            \"ALERT_LEAVE_CHAT\":\"Sohbetten ayrılmak istiyor musunuz?\",\n            \"YES\":\"Evet\",\n            \"NO\":\"Hayır\",\n            \"BUTTON_CLOSE_TO_ICON\":\"Sohbeti küçültün\",\n            \"BUTTON_EDIT_PROFILE\":\"Profili güncelle\",\n            \"RATE_CHAT\":\"Sohbeti derecelendir\",\n            \"WELCOME_TITLE\":\"Merhaba Tiledesk’e hoş geldiniz. 👋\",\n            \"WELCOME_MSG\":\"Size nasıl yardımcı olabiliriz?\",\n            \"WELCOME\":\"Hoş geldiniz.\",\n            \"OPTIONS\":\"Seçenekler\",\n            \"SOUND_OFF\":\"Ses kapalı\",\n            \"SOUND_ON\":\"Ses açık\",\n            \"LOGOUT\":\"Çıkış yap\",\n            \"CLOSE\":\"Kapat\",\n            \"RESTART\":\"Tekrar başlat\",\n            \"PREV_CONVERSATIONS\":\"Konuşmalarınız\",\n            \"YOU\":\"Siz\",\n            \"SHOW_ALL_CONV\":\"Hepsini göster\",\n            \"START_A_CONVERSATION\":\"Bir konuşma başlatın\",\n            \"NO_CONVERSATION\":\"Konuşma yok\",\n            \"SEE_PREVIOUS\":\"Öncekine bak\",\n            \"WAITING_TIME_FOUND\":\"Ekip genellikle …… içerisinde cevaplar $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\":\"Ekip en kısa süre içerisinde cevap verecektir.\",\n            \"CLOSED\":\"Kapali\",\n            \"CLOSE_CHAT\": \"Sohbeti kapat\",\n            \"MINIMIZE\":\"Küçült\",\n            \"MAXIMIZE\":\"Büyüt\",\n            \"CONFIRM_CLOSE_CHAT\":\"Bu sohbeti kapatmak istediğinizden emin misiniz?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"siz\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"eklendiniz \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"katıldı\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"sohbet etmek\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Sohbet yeniden açıldı\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Sohbet kapatıldı\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Potansiyel müşteri güncellendi\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"gruptan kaldırıldı\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"görüşmeden ayrıldı\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Size yeni bir destek talebi atandı\",\n           \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Tam ad\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-posta\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Geçersiz e-posta adresi\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefon\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefon gerekli\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Destek ekibine mesajınız\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Konuşmaya devam etmeden önce lütfen <a href='https://tiledesk.com/termsofservice/' target='_blank'>Şartlarımızı</a> ve <a href='https:/ kabul edin. /tiledesk.com/privacy.html' target='_blank'>Gizlilik Politikası</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Kabul ediyorum\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Bu alan gereklidir\",\n            \n            \"TICKET_TAKING\":\"Talep alındı ve yardım ekibi bununla ilgileniyor.\\nDaha fazla yorum eklemek için bu e-postayı yanıtlayın.\",\n\n            \"LABEL_TODAY\": \"bugün\",\n            \"LABEL_TOMORROW\": \"dün\",\n            \"LABEL_LAST_ACCESS\": \"son giriş\",\n            \"LABEL_TO\": \"de\",\n            \"ARRAY_DAYS\": [\"Pazartesi\", \"Salı\", \"Çarşamba\", \"Perşembe\", \"Cuma\", \"Cumartesi\", \"Pazar\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"bir ek gönderdi\",\n            \"SENT_AN_IMAGE\": \"bir resim gönderdi\",\n    \n            \"LABEL_PREVIEW\": \"Ön izleme\",\n            \"SWITCH_TO\":\"Veya geçiş yapın:\",\n\t\t\t\"MAX_ATTACHMENT\": \"İzin verilen maksimum boyut {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Dosya izin verilen maksimum boyutu aşıyor\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n\n\n    {       \n        \"lang\":\"SR\",\n        \"data\":{\n            \"LABEL_PLACEHOLDER\":\"откуцај своју поруку..\",\n            \"LABEL_START_NW_CONV\":\"Нови разговор\",\n            \"LABEL_FIRST_MSG\":\"Опишите укратко свој проблем, контактираће вас агент.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\":\"🤔  Сви оператери су тренутно ван мреже. У сваком случају можете описати свој проблем. Биће додељен тиму за подршку који ће вам се јавити што је пре могуће.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\":\"🤔  Наше канцеларије су затворене. У сваком случају можете описати свој проблем. Биће додељен тиму за подршку који ће вам се јавити што је пре могуће.\",\n            \"LABEL_WHATSAPP\": \"Пошаљите нам поруку\",\n            \"LABEL_SELECT_TOPIC\":\"Изаберите тему\",\n            \"LABEL_COMPLETE_FORM\":\"Попуните образац да бисте започели разговор са следећим доступним агентом.\",\n            \"LABEL_FIELD_NAME\":\"Име\",\n            \"LABEL_ERROR_FIELD_NAME\":\"Обавезно поље (минимално 5 знакова).\",\n            \"LABEL_FIELD_EMAIL\":\"Емаил\",\n            \"LABEL_ERROR_FIELD_EMAIL\":\"Унесите исправну е-маил адресу.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\":\"Обавезно поље\",\n            \"LABEL_WRITING\":\"је писање...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Пошаљите нову поруку\",\n            \"AGENT_NOT_AVAILABLE\":\"Оффлине\",\n            \"AGENT_AVAILABLE\":\"Онлине\",\n            \"GUEST_LABEL\":\"Гост\",\n            \"ALL_AGENTS_OFFLINE_LABEL\":\"Сви оператери су тренутно ван мреже\",\n            \"LABEL_LOADING\":\"Учитавање...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\":\"🖐 Треба помоћ?\",\n            \"CALLOUT_MSG_PLACEHOLDER\":\"Кликните овде и почните да ћаскате са нама!\",\n            \"CUSTOMER_SATISFACTION\":\"Задовољство купаца\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\":\"Ваше мишљење о нашој служби за кориснике\",\n            \"DOWNLOAD_TRANSCRIPT\":\"Преузмите транскрипт\",\n            \"BACK\":\"Назад\",\n            \"CONTINUE\": \"Настави\",\n            \"YOUR_RATING\":\"твој рејтинг\",\n            \"WRITE_YOUR_OPINION\":\"Напишите своје мишљење ... (опционо)\",\n            \"SUBMIT\":\"Субмит\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\":\"Хвала вам на оцени\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\":\"ваша рејтинг је примљена\",\n            \"ALERT_LEAVE_CHAT\":\"Да ли желите да напустите chat?\",\n            \"YES\":\"Да\",\n            \"NO\":\"Не\",\n            \"BUTTON_CLOSE_TO_ICON\":\"Минимизирајте chat\",\n            \"BUTTON_EDIT_PROFILE\":\"Ажурирање профил\",\n            \"RATE_CHAT\":\"Рате chat\",\n            \"WELCOME_TITLE\":\"Здраво, добродошли у Tiledesk 👋\",\n            \"WELCOME_MSG\":\"How can we help?\",\n            \"WELCOME\":\"Добродошли\",\n            \"OPTIONS\":\"опције\",\n            \"SOUND_OFF\":\"Звук искључен\",\n            \"SOUND_ON\":\"Звук укључен\",\n            \"LOGOUT\":\"Одјавити се\",\n            \"CLOSE\":\"Затвори\",\n            \"RESTART\":\"Поново покренути\",\n            \"PREV_CONVERSATIONS\":\"Ваши разговори\",\n            \"YOU\":\"Ти\",\n            \"SHOW_ALL_CONV\":\"прикажи све\",\n            \"START_A_CONVERSATION\":\"Започните разговор\",\n            \"NO_CONVERSATION\":\"Нема разговора\",\n            \"SEE_PREVIOUS\":\"види претходну\",\n            \"WAITING_TIME_FOUND\":\"Тим обично одговара у $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\":\"Тим ће одговорити у најкраћем могућем року\",\n            \"CLOSED\":\"Затворено\",\n            \"CLOSE_CHAT\": \"Затвори ћаскање\",\n            \"MINIMIZE\":\"Минимизирајте\",\n            \"MAXIMIZE\":\"Максимизирајте\",\n            \"CONFIRM_CLOSE_CHAT\":\"Да ли сте сигурни да желите да затворите ово ћаскање?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\":\"ти\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\":\"додани сте\",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\":\"тхе chat\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\":\"придружио\",\n            \"INFO_SUPPORT_CHAT_REOPENED\":\"Chat поново отворен\",\n            \"INFO_SUPPORT_CHAT_CLOSED\":\"Chat затворено\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Лид је ажуриран\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"уклоњен из групе\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"је напустио разговор\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Додељен вам је нови захтев за подршку\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\":\"Пун назив\",\n            \"LABEL_PRECHAT_USER_EMAIL\":\"Email\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\":\"Неважећа адреса е-поште\",\n            \"LABEL_PRECHAT_USER_PHONE\":\"Телефон\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\":\"Телефон је обавезан\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\":\"Ваша порука за тим за подршку\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\":\"Пре него што наставите у разговору, прихватите наше <a href='https://tiledesk.com/termsofservice/' target='_blank'>Услове</a> и <a href='https://tiledesk.com/privacy.html' target='_blank'>Политика приватности</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\":\"Слазем се\",\n            \"PRECHAT_REQUIRED_ERROR\":\"Ово поље је захтевано\",\n            \n            \"TICKET_TAKING\":\"Захтјев је примљен и помоћно особље се бави њиме.\\nДа бисте додали још коментара, одговорите на ову е-поруку.\",\n            \n            \"LABEL_TODAY\": \"данас\",\n            \"LABEL_TOMORROW\": \"јуче\",\n            \"LABEL_LAST_ACCESS\": \"последњи приступ\",\n            \"LABEL_TO\": \"ат\",\n            \"ARRAY_DAYS\": [\"Понедељак\", \"Уторак\", \"Среда\", \"Четвртак\", \"Петак\", \"Субота\", \"Недеља\"],\n            \n            \"SENT_AN_ATTACHMENT\":\"послао прилог\",\n            \"SENT_AN_IMAGE\":\"послао имаге\",\n            \n            \"LABEL_PREVIEW\": \"Преглед\",\n            \"SWITCH_TO\":\"Или пређите на:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Максимално дозвољена величина {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Фајл прелази максимално дозвољену величину\",\n    \t\t\"EMOJI\": \"Емоџи\"\n        }\n     },\n\n\n     {\n     \"lang\":\"AR\",\n        \"data\": {\n            \"LABEL_PLACEHOLDER\": \"اكتب رسالتك ..\",\n            \"LABEL_START_NW_CONV\": \"محادثة جديدة\",\n            \"LABEL_FIRST_MSG\": \"صِف مشكلتك بعد قليل ، وسيتواصل معك أحد الوكلاء.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 جميع المشغلين غير متصلين في الوقت الحالي. يمكنك على أي حال وصف مشكلتك. سيتم تعيينه لفريق الدعم الذي سيعود إليك في أقرب وقت ممكن.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 مكاتبنا مغلقة. يمكنك على أي حال وصف مشكلتك. سيتم تعيينه لفريق الدعم الذي سيعود إليك في أقرب وقت ممكن.\",\n            \"LABEL_WHATSAPP\": \"راسلنا\",\n            \"LABEL_SELECT_TOPIC\": \"اختر عنوانا\",\n            \"LABEL_COMPLETE_FORM\": \"أكمل النموذج لبدء محادثة مع الوكيل المتاح التالي.\",\n            \"LABEL_FIELD_NAME\": \"اسم\",\n            \"LABEL_ERROR_FIELD_NAME\": \"حقل مطلوب (بحد أدنى 5 أحرف).\",\n            \"LABEL_FIELD_EMAIL\": \"البريد الإلكتروني\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"أدخل عنوان بريد إلكتروني صالح.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"يتطلب حقلا\",\n            \"LABEL_WRITING\": \"يكتب...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"إرسال رسالة جديدة\",\n            \"AGENT_NOT_AVAILABLE\": \" غير متصل على الانترنت\",\n            \"AGENT_AVAILABLE\": \" متصل\",\n            \"GUEST_LABEL\": \"زائر\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"جميع المشغلين غير متصلون في الوقت الحالي\",\n            \"LABEL_LOADING\": \"جار التحميل...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 بحاجة الى مساعدة؟\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"انقر هنا وابدأ الدردشة معنا!\",\n            \"CUSTOMER_SATISFACTION\": \"رضا العملاء\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"رأيك في خدمة العملاء لدينا\",\n            \"DOWNLOAD_TRANSCRIPT\": \"تنزيل النص\",\n            \"BACK\": \"خلف\",\n            \"CONTINUE\": \"يكمل\",\n            \"YOUR_RATING\": \"تقييمك\",\n            \"WRITE_YOUR_OPINION\": \"أكتب رأيك ... (اختياري)\",\n            \"SUBMIT\": \"يُقدِّم\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"شكرا لتقييمك\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"لقد تم استلام تقييمك\",\n            \"ALERT_LEAVE_CHAT\": \"هل تريد مغادرة الدردشة؟\",\n            \"YES\": \"نعم\",\n            \"NO\": \"لا\",\n            \"BUTTON_CLOSE_TO_ICON\": \"تصغير الدردشة\",\n            \"BUTTON_EDIT_PROFILE\": \"تحديث الملف\",\n            \"RATE_CHAT\": \"معدل الدردشة\",\n            \"WELCOME_TITLE\": \"مرحبًا ، مرحبًا بك في Tiledesk 👋\",\n            \"WELCOME_MSG\": \"كيف يمكن أن نساعد؟\",\n            \"WELCOME\": \"أهلا وسهلا\",\n            \"OPTIONS\": \"والخيارات\",\n            \"SOUND_OFF\": \"الصوت مقفل\",\n            \"SOUND_ON\": \"الصوت مفعل\",\n            \"LOGOUT\": \"تسجيل خروج\",\n            \"CLOSE\": \"قريب\",\n            \"RESTART\":\"إعادة بدء\",\n            \"PREV_CONVERSATIONS\": \"محادثاتك\",\n            \"YOU\": \"أنت\",\n            \"SHOW_ALL_CONV\": \"عرض الكل\",\n            \"START_A_CONVERSATION\": \"بدء محادثة\",\n            \"NO_CONVERSATION\": \"لا محادثة\",\n            \"SEE_PREVIOUS\": \"انظر السابق\",\n            \"WAITING_TIME_FOUND\": \"يرد الفريق عادةً بـ $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"سيقوم الفريق بالرد في أقرب وقت ممكن\",\n            \"CLOSED\": \"مغلق\",\n            \"CLOSE_CHAT\": \"إغلاق الدردشة\",\n            \"MINIMIZE\":\"تصغير\",\n            \"MAXIMIZE\":\"تحقيق أقصى قدر\",\n            \"CONFIRM_CLOSE_CHAT\":\"هل أنت متأكد أنك تريد إغلاق هذه الدردشة؟\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"أنت\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"لقد تم اضافتك \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"المحادثة\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"انضم\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"تمت إعادة فتح الدردشة\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"الدردشة مغلقة\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"تحديث الرصاص\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"تمت إزالته من المجموعة\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"غادر المحادثات\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"تم تعيين طلب دعم جديد لك\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"الاسم الكامل\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"البريد الإلكتروني\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"عنوان البريد الإلكتروني غير صالح\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"هاتف\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"الهاتف مطلوب\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"رسالتك لفريق الدعم\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"قبل متابعة المحادثة ، يرجى الموافقة على سياسة الخصوصية الخاصة بشروط <a href='https://tiledesk.com/termsofservice/' target='_blank'></a> و <a href='https://tiledesk.com/privacy.html' target='_blank'></a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"أنا موافق\",\n            \"PRECHAT_REQUIRED_ERROR\": \"هذه الخانة مطلوبه\",\n            \n            \"TICKET_TAKING\": \"تم استلام الطلب ويقوم طاقم المساعدة بالتعامل معه.\\nلإضافة المزيد من التعليقات ، قم بالرد على هذا البريد الإلكتروني.\",\n            \n            \"LABEL_TODAY\": \"اليوم\",\n            \"LABEL_TOMORROW\": \"في الامس\",\n            \"LABEL_LAST_ACCESS\": \"آخر ولوج\",\n            \"LABEL_TO\": \"في\",\n            \"ARRAY_DAYS\": [\"الاثنين\", \"يوم الثلاثاء\", \"الأربعاء\", \"يوم الخميس\", \"جمعة\", \"السبت\", \"الأحد\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"أرسل مرفقًا\",\n            \"SENT_AN_IMAGE\": \"أرسل صورة\",\n            \n            \"LABEL_PREVIEW\": \"معاينة\",\n            \"SWITCH_TO\": \"أو قم بالتبديل إلى:\",\n\t\t\t\"MAX_ATTACHMENT\": \"الحد الأقصى للحجم المسموح به {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"الملف يتجاوز الحد الأقصى المسموح به\",\n    \t\t\"EMOJI\": \"رموز تعبيرية\"\n        }\n    },\n\n    {\n    \"lang\":\"UK\",\n    \"data\": \n        {\n            \"LABEL_PLACEHOLDER\": \"введіть своє повідомлення..\",\n            \"LABEL_START_NW_CONV\": \"Нова розмова\",\n            \"LABEL_FIRST_MSG\": \"Коротко опишіть вашу проблему, з вами зв’яжеться агент.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Усі оператори на даний момент офлайн. Ви все одно можете описати свою проблему. Його буде призначено команді підтримки, яка зв’яжеться з вами якомога швидше.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Наші офіси зачинені. Ви все одно можете описати свою проблему. Його буде призначено команді підтримки, яка зв’яжеться з вами якомога швидше.\",\n            \"LABEL_WHATSAPP\": \"Напишіть нам\",\n            \"LABEL_SELECT_TOPIC\": \"Виберіть тему\",\n            \"LABEL_COMPLETE_FORM\": \"Заповніть форму, щоб почати розмову з наступним доступним агентом.\",\n            \"LABEL_FIELD_NAME\": \"Ім'я\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Обов’язкове поле (мінімум 5 символів).\",\n            \"LABEL_FIELD_EMAIL\": \"Електронна пошта\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Введіть дійсну адресу електронної пошти.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Обов'язкове поле\",\n            \"LABEL_WRITING\": \"пише...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Надіслати нове повідомлення\",\n            \"AGENT_NOT_AVAILABLE\": \" Офлайн\",\n            \"AGENT_AVAILABLE\": \" Онлайн\",\n            \"GUEST_LABEL\": \"Гість\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"На даний момент всі оператори офлайн\",\n            \"LABEL_LOADING\": \"Завантаження...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Потрібна допомога?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Натисніть тут і почніть спілкуватися з нами!\",\n            \"CUSTOMER_SATISFACTION\": \"Задоволеності клієнтів\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Ваша думка про наше обслуговування клієнтів\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Завантажити стенограму\",\n            \"BACK\": \"Назад\",\n            \"CONTINUE\": \"Продовжити\",\n            \"YOUR_RATING\": \"ваш рейтинг\",\n            \"WRITE_YOUR_OPINION\": \"Напишіть свою думку ... (необов'язково)\",\n            \"SUBMIT\": \"Подати\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Дякуємо за оцінку\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"вашу оцінку отримано\",\n            \"ALERT_LEAVE_CHAT\": \"Ви хочете вийти з чату?\",\n            \"YES\": \"Так\",\n            \"NO\": \"Ні\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Мінімізуйте чат\",\n            \"BUTTON_EDIT_PROFILE\": \"Оновити профіль\",\n            \"RATE_CHAT\": \"Оцініть чат\",\n            \"WELCOME_TITLE\": \"Привіт, ласкаво просимо до Tiledesk 👋\",\n            \"WELCOME_MSG\": \"Як ми можемо допомогти?\",\n            \"WELCOME\": \"Ласкаво просимо\",\n            \"OPTIONS\": \"варіанти\",\n            \"SOUND_OFF\": \"Звук вимкнено\",\n            \"SOUND_ON\": \"Звук увімкнено\",\n            \"LOGOUT\": \"Вийти\",\n            \"CLOSE\": \"Закрити\",\n            \"RESTART\":\"Перезапустіть\",\n            \"PREV_CONVERSATIONS\": \"Ваші розмови\",\n            \"YOU\": \"ти\",\n            \"SHOW_ALL_CONV\": \"показати все\",\n            \"START_A_CONVERSATION\": \"Почніть розмову\",\n            \"NO_CONVERSATION\": \"Без розмови\",\n            \"SEE_PREVIOUS\": \"див. попереднє\",\n            \"WAITING_TIME_FOUND\": \"Команда зазвичай відповідає  час відповіді\",\n            \"WAITING_TIME_NOT_FOUND\": \"Команда відповість якомога швидше\",\n            \"CLOSED\": \"ЗАЧИНЕНО\",\n            \"CLOSE_CHAT\": \"Закрити чат\",\n            \"MINIMIZE\":\"Згорнути\",\n            \"MAXIMIZE\":\"Максимізувати\",\n            \"CONFIRM_CLOSE_CHAT\":\"Ви впевнені, що бажаєте закрити цей чат?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"ти\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"вас додали \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"чат\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"приєднався\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Чат знову відкрився\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Чат закритий\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Лід оновлено\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"видалено з групи\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"залишив бесіду\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Вам призначено новий запит на підтримку\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Повне ім'я\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Електронна пошта\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"невірна адреса електронної пошти\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Телефон\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Потрібен телефон\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Ваше повідомлення для служби підтримки\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Перш ніж продовжити розмову, будь ласка, погодьтеся з нашими <a href='https://tiledesk.com/termsofservice/' target='_blank'>Умовами</a> і <a href='https://tiledesk.com/privacy.html' target='_blank'>Політикою конфіденційності</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"я згоден\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Це поле є обов'язковим\",\n            \n            \"TICKET_TAKING\": \"Запит отримано, і допоміжний персонал працює з ним.\\nЩоб додати більше коментарів, надішліть відповідь на цей електронний лист.\",\n            \n            \"LABEL_TODAY\": \"сьогодні\",\n            \"LABEL_TOMORROW\": \"вчора\",\n            \"LABEL_LAST_ACCESS\": \"вчора\",\n            \"LABEL_TO\": \"в\",\n            \"ARRAY_DAYS\": [\"Понеділок\", \"Вівторок\", \"Середа\", \"Четвер\", \"П'ятниця\", \"Субота\", \"Неділя\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"надіслав вкладений файл\",\n            \"SENT_AN_IMAGE\": \"надіслав зображення\",\n            \n            \"LABEL_PREVIEW\": \"Попередній перегляд\",\n            \"SWITCH_TO\": \"Або перейдіть на:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Максимально допустимий розмір {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Файл перевищує максимально допустимий розмір\",\n    \t\t\"EMOJI\": \"Емодзі\"\n        }\n    },\n    {\n        \"lang\":\"SV\",\n        \"data\": \n        {\n            \"LABEL_PLACEHOLDER\": \"skriv ditt meddelande..\",\n            \"LABEL_START_NW_CONV\": \"Ny konversation\",\n            \"LABEL_FIRST_MSG\": \"Beskriv kort ditt problem, du kommer att kontaktas av en agent.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Alla operatörer är offline för tillfället. Du kan hur som helst beskriva ditt problem. Det kommer att tilldelas supportteamet som återkommer till dig så snart som möjligt.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Våra kontor är stängda. Du kan hur som helst beskriva ditt problem. Det kommer att tilldelas supportteamet som återkommer till dig så snart som möjligt.\",\n            \"LABEL_WHATSAPP\": \"Meddela oss\",\n            \"LABEL_SELECT_TOPIC\": \"Välj ett ämne\",\n            \"LABEL_COMPLETE_FORM\": \"Fyll i formuläret för att starta en konversation med nästa tillgängliga agent.\",\n            \"LABEL_FIELD_NAME\": \"namn\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Obligatoriskt fält (minst 5 tecken).\",\n            \"LABEL_FIELD_EMAIL\": \"E-post\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Ange en giltig e-postadress.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"obligatoriskt fält\",\n            \"LABEL_WRITING\": \"skriver...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Skicka ett nytt meddelande\",\n            \"AGENT_NOT_AVAILABLE\": \" Off-line\",\n            \"AGENT_AVAILABLE\": \" Uppkopplad\",\n            \"GUEST_LABEL\": \"Gäst\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Alla operatörer är offline för tillfället\",\n            \"LABEL_LOADING\": \"Läser in...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Behöver du hjälp?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Klicka här och börja chatta med oss!\",\n            \"CUSTOMER_SATISFACTION\": \"Kundnöjdhet\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Din åsikt om vår kundtjänst\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Ladda ner avskrift\",\n            \"BACK\": \"Tillbaka\",\n            \"CONTINUE\": \"Fortsätta\",\n            \"YOUR_RATING\": \"ditt betyg\",\n            \"WRITE_YOUR_OPINION\": \"Skriv din åsikt ... (valfritt)\",\n            \"SUBMIT\": \"Skicka in\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Tack för din utvärdering\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"ditt betyg har mottagits\",\n            \"ALERT_LEAVE_CHAT\": \"Vill du lämna chatten?\",\n            \"YES\": \"Ja\",\n            \"NO\": \"Nej\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Minimera chatten\",\n            \"BUTTON_EDIT_PROFILE\": \"Uppdatera profil\",\n            \"RATE_CHAT\": \"Betygsätt chatt\",\n            \"WELCOME_TITLE\": \"Hej, välkommen till Tiledesk 👋\",\n            \"WELCOME_MSG\": \"Hur kan vi hjälpa?\",\n            \"WELCOME\": \"Välkommen\",\n            \"OPTIONS\": \"alternativ\",\n            \"SOUND_OFF\": \"Ljud av\",\n            \"SOUND_ON\": \"Ljud på\",\n            \"LOGOUT\": \"Logga ut\",\n            \"CLOSE\": \"Stänga\",\n            \"RESTART\":\"Omstart\",\n            \"PREV_CONVERSATIONS\": \"Dina konversationer\",\n            \"YOU\": \"Du\",\n            \"SHOW_ALL_CONV\": \"visa allt\",\n            \"START_A_CONVERSATION\": \"Starta en konversation\",\n            \"NO_CONVERSATION\": \"Ingen konversation\",\n            \"SEE_PREVIOUS\": \"se föregående\",\n            \"WAITING_TIME_FOUND\": \"Teamet svarar vanligtvis med $reply_time\",\n            \"WAITING_TIME_NOT_FOUND\": \"Teamet kommer att svara så snart som möjligt\",\n            \"CLOSED\": \"Stängd\",\n            \"CLOSE_CHAT\":\"Stäng chatten\",\n            \"MINIMIZE\":\"Minimera\",\n            \"MAXIMIZE\":\"Maximera\",\n            \"CONFIRM_CLOSE_CHAT\":\"Är du säker på att du vill stänga den här chatten?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"du\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"du har lagts till \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"chatten\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"gick med\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Chatten öppnades igen\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Chatten stängd\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Lead uppdaterad\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"borttagen från gruppen\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"har lämnat konversationen\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"En ny supportförfrågan har tilldelats dig\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Fullständiga namn\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-post\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Ogiltig e-postadress\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefon\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefon krävs\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Ditt meddelande till supportteamet\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Innan du fortsätter i konversationen, godkänn våra <a href='https://tiledesk.com/termsofservice/' target='_blank'>Villkor</a> och <a href='https://tiledesk.com/privacy.html' target='_blank'>Sekretesspolicy</a>\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"jag håller med\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Detta fält är obligatoriskt\",\n            \n            \"TICKET_TAKING\": \"Begäran har kommit in och assistanspersonalen hanterar den.\\nFör att lägga till fler kommentarer, svara på det här e-postmeddelandet.\",\n            \n            \"LABEL_TODAY\": \"i dag\",\n            \"LABEL_TOMORROW\": \"i går\",\n            \"LABEL_LAST_ACCESS\": \"senaste åtkomst\",\n            \"LABEL_TO\": \"på\",\n            \"ARRAY_DAYS\": [\"måndag\", \"tisdag\", \"onsdag\", \"torsdag\", \"fredag\", \"lördag\", \"söndag\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"skickade en bilaga\",\n            \"SENT_AN_IMAGE\": \"skickade en bild\",\n            \n            \"LABEL_PREVIEW\": \"Förhandsvisning\",\n            \"SWITCH_TO\": \"Eller byt till:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Max tillåten storlek {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Filen överskrider den maximalt tillåtna storleken\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\":\"KK\",\n        \"data\": \n        {\n            \"LABEL_PLACEHOLDER\": \"хабарламаңызды теріңіз..\",\n            \"LABEL_START_NW_CONV\": \"Жаңа әңгіме\",\n            \"LABEL_FIRST_MSG\": \"Мәселеңізді қысқаша сипаттаңыз, сізге агент хабарласады.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Қазіргі уақытта барлық операторлар желіден тыс. Сіз бәрібір мәселеңізді сипаттай аласыз. Ол сізге мүмкіндігінше тезірек хабарласатын қолдау көрсету тобына тағайындалады.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Біздің кеңселер жабық. Сіз бәрібір мәселеңізді сипаттай аласыз. Ол сізге мүмкіндігінше тезірек хабарласатын қолдау көрсету тобына тағайындалады.\",\n            \"LABEL_WHATSAPP\": \"Бізге хабарлаңыз\",\n            \"LABEL_SELECT_TOPIC\": \"Тақырыпты таңдаңыз\",\n            \"LABEL_COMPLETE_FORM\": \"Келесі қолжетімді агентпен сөйлесуді бастау үшін пішінді толтырыңыз.\",\n            \"LABEL_FIELD_NAME\": \"Аты\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Міндетті өріс (кемінде 5 таңба).\",\n            \"LABEL_FIELD_EMAIL\": \"Электрондық пошта\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Жарамды электрондық пошта мекенжайын енгізіңіз.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Міндетті өріс\",\n            \"LABEL_WRITING\": \"жазып жатыр...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Жаңа хабарлама жіберу\",\n            \"AGENT_NOT_AVAILABLE\": \" Офлайн\",\n            \"AGENT_AVAILABLE\": \" Желіде\",\n            \"GUEST_LABEL\": \"Қонақ\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Қазіргі уақытта барлық операторлар желіден тыс\",\n            \"LABEL_LOADING\": \"Жүктелуде...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Көмек керек пе?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Мұнда басыңыз және бізбен сөйлесуді бастаңыз!\",\n            \"CUSTOMER_SATISFACTION\": \"Тұтынушының қанағаттануы\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Біздің тұтынушыларға қызмет көрсету туралы пікіріңіз\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Транскриптті жүктеп алыңыз\",\n            \"BACK\": \"Артқа\",\n            \"CONTINUE\": \"Жалғастыру\",\n            \"YOUR_RATING\": \"сіздің рейтингіңіз\",\n            \"WRITE_YOUR_OPINION\": \"Өз пікіріңізді жазыңыз ... (міндетті емес)\",\n            \"SUBMIT\": \"Жіберу\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Бағаңыз үшін рахмет\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"сіздің рейтингіңіз алынды\",\n            \"ALERT_LEAVE_CHAT\": \"Чаттан кеткіңіз келе ме?\",\n            \"YES\": \"Иә\",\n            \"NO\": \"Жоқ\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Сөйлесуді азайту\",\n            \"BUTTON_EDIT_PROFILE\": \"Профильді жаңарту\",\n            \"RATE_CHAT\": \"Чатқа баға беріңіз\",\n            \"WELCOME_TITLE\": \"Сәлем, Tiledesk сайтына қош келдіңіз 👋\",\n            \"WELCOME_MSG\": \"Біз қалай көмектесе аламыз?\",\n            \"WELCOME\": \"Қош келдіңіз\",\n            \"OPTIONS\": \"опциялар\",\n            \"SOUND_OFF\": \"Дыбыс өшірулі\",\n            \"SOUND_ON\": \"Дыбыс қосулы\",\n            \"LOGOUT\": \"Шығу\",\n            \"CLOSE\": \"Жабық\",\n            \"RESTART\":\"Қайтадан қосу\",\n            \"PREV_CONVERSATIONS\": \"Сіздің сұхбаттарыңыз\",\n            \"YOU\": \"Сіз\",\n            \"SHOW_ALL_CONV\": \"барлығын көрсету\",\n            \"START_A_CONVERSATION\": \"Әңгіме бастаңыз\",\n            \"NO_CONVERSATION\": \"Әңгіме жоқ\",\n            \"SEE_PREVIOUS\": \"алдыңғы қараңыз\",\n            \"WAITING_TIME_FOUND\": \"Топ әдетте $reply_time-мен жауап береді\",\n            \"WAITING_TIME_NOT_FOUND\": \"Команда мүмкіндігінше тезірек жауап береді\",\n            \"CLOSED\": \"Жабық\",\n            \"CLOSE_CHAT\":\"Чатты жабу\",\n            \"MINIMIZE\":\"Кішірейту\",\n            \"MAXIMIZE\":\"Үлкейту\",\n            \"CONFIRM_CLOSE_CHAT\":\"Бұл чатты шынымен жапқыңыз келе ме?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"сен\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"сіз қосылдыңыз \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"чат\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"қосылды\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Чат қайта ашылды\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Чат жабылды\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Жетекші жаңартылды\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"топтан шығарылды\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"әңгімеден шығып кетті\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Сізге жаңа қолдау сұрауы тағайындалды\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Толық аты\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Электрондық пошта\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Жарамсыз электрондық пошта мекенжайы\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Телефон\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Телефон қажет\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Қолдау көрсету тобына арналған хабарламаңыз\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Әңгімені жалғастырмас бұрын, <a href='https://tiledesk.com/termsofservice/' target='_blank'></a> және <a href='https://tiledesk.com/privacy.html' target='_blank'>-шарттар Құпиялылық саясаты</a>-пен келісесіз\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Мен келісемін\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Бұл керек аймақ\",\n            \n            \"TICKET_TAKING\": \"Өтініш қабылданды және көмек көрсету қызметкерлері онымен айналысуда.\\nҚосымша пікірлер қосу үшін осы электрондық поштаға жауап беріңіз.\",\n            \n            \"LABEL_TODAY\": \"бүгін\",\n            \"LABEL_TOMORROW\": \"кеше\",\n            \"LABEL_LAST_ACCESS\": \"соңғы рұқсат\",\n            \"LABEL_TO\": \"сағ\",\n            \"ARRAY_DAYS\": [\"Дүйсенбі\", \"сейсенбі\", \"сәрсенбі\", \"бейсенбі\", \"жұма\", \"сенбі\", \"жексенбі\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"қосымша жіберді\",\n            \"SENT_AN_IMAGE\": \"сурет жіберді\",\n            \n            \"LABEL_PREVIEW\": \"Алдын ала қарау\",\n            \"SWITCH_TO\": \"Немесе келесіге ауысыңыз:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Максималды рұқсат етілген өлшем {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Файл максималды рұқсат етілген өлшемнен асады\",\n    \t\t\"EMOJI\": \"Эмодзи\"\n        }\n    },\n    {\n        \"lang\":\"UZ\",\n        \"data\": \n        {\n            \"LABEL_PLACEHOLDER\": \"xabaringizni yozing..\",\n            \"LABEL_START_NW_CONV\": \"Yangi suhbat\",\n            \"LABEL_FIRST_MSG\": \"Muammoingizni qisqacha tasvirlab bering, siz bilan agent bog'lanadi.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Hozirda barcha operatorlar oflayn. Qanday bo'lmasin, muammoingizni tasvirlashingiz mumkin. Bu sizga imkon qadar tezroq qaytib keladigan qo'llab-quvvatlash guruhiga tayinlanadi.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Bizning ofislarimiz yopiq. Qanday bo'lmasin, muammoingizni tasvirlashingiz mumkin. Bu sizga imkon qadar tezroq qaytib keladigan qo'llab-quvvatlash guruhiga tayinlanadi.\",\n            \"LABEL_WHATSAPP\": \"Bizga xabar yuboring\",\n            \"LABEL_SELECT_TOPIC\": \"Mavzuni tanlang\",\n            \"LABEL_COMPLETE_FORM\": \"Keyingi mavjud agent bilan suhbatni boshlash uchun shaklni to'ldiring.\",\n            \"LABEL_FIELD_NAME\": \"Ism\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Majburiy maydon (kamida 5 ta belgi).\",\n            \"LABEL_FIELD_EMAIL\": \"Elektron pochta\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Yaroqli elektron pochta manzilini kiriting.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"Majburiy maydon\",\n            \"LABEL_WRITING\": \"yozmoqda...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Yangi xabar yuboring\",\n            \"AGENT_NOT_AVAILABLE\": \" Oflayn\",\n            \"AGENT_AVAILABLE\": \" Onlayn\",\n            \"GUEST_LABEL\": \"Mehmon\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Ayni paytda barcha operatorlar oflayn rejimda\",\n            \"LABEL_LOADING\": \"Yuklanmoqda...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Yordam kerakmi?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Bu yerni bosing va biz bilan suhbatni boshlang!\",\n            \"CUSTOMER_SATISFACTION\": \"Mijozlarning qoniqishi\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Mijozlarga xizmat ko'rsatish haqidagi fikringiz\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Transkriptni yuklab oling\",\n            \"BACK\": \"Orqaga\",\n            \"CONTINUE\": \"Davom eting\",\n            \"YOUR_RATING\": \"sizning reytingingiz\",\n            \"WRITE_YOUR_OPINION\": \"Fikringizni yozing ... (ixtiyoriy)\",\n            \"SUBMIT\": \"Yuborish\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Baholaganingiz uchun tashakkur\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"Sizning reytingingiz olindi\",\n            \"ALERT_LEAVE_CHAT\": \"Chatni tark etmoqchimisiz?\",\n            \"YES\": \"Ha\",\n            \"NO\": \"Yo'q\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Suhbatni minimallashtirish\",\n            \"BUTTON_EDIT_PROFILE\": \"Profilni yangilash\",\n            \"RATE_CHAT\": \"Suhbatni baholang\",\n            \"WELCOME_TITLE\": \"Salom, Tiledesk 👋 ga xush kelibsiz\",\n            \"WELCOME_MSG\": \"Biz qanday yordam bera olamiz?\",\n            \"WELCOME\": \"Xush kelibsiz\",\n            \"OPTIONS\": \"variantlari\",\n            \"SOUND_OFF\": \"Ovoz o'chirilgan\",\n            \"SOUND_ON\": \"Ovoz yoqilgan\",\n            \"LOGOUT\": \"Chiqish\",\n            \"CLOSE\": \"Yopish\",\n            \"RESTART\":\"Qayta ishga tushirish\",\n            \"PREV_CONVERSATIONS\": \"Sizning suhbatlaringiz\",\n            \"YOU\": \"Siz\",\n            \"SHOW_ALL_CONV\": \"hammasini ko'rsatish\",\n            \"START_A_CONVERSATION\": \"Suhbatni boshlang\",\n            \"NO_CONVERSATION\": \"Suhbat yo'q\",\n            \"SEE_PREVIOUS\": \"oldingisiga qarang\",\n            \"WAITING_TIME_FOUND\": \"Jamoa odatda $reply_timebilan javob beradi\",\n            \"WAITING_TIME_NOT_FOUND\": \"Jamoa imkon qadar tezroq javob beradi\",\n            \"CLOSED\": \"Yopiq\",\n            \"CLOSE_CHAT\":\"Chatni yopish\",\n            \"MINIMIZE\":\"Minimallashtirish\",\n            \"MAXIMIZE\":\"Maksimallashtirish\",\n            \"CONFIRM_CLOSE_CHAT\":\"Bu chatni yopmoqchi ekanligingizga ishonchingiz komilmi?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"siz\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"qo'shildingiz \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"suhbat\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"qo'shildi\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Chat qayta ochildi\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Chat yopildi\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Etakchi yangilandi\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"guruhdan olib tashlandi\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"suhbatni tark etdi\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Sizga yangi qoʻllab-quvvatlash soʻrovi tayinlandi\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"To'liq ismi sharif\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"Elektron pochta\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"Yaroqsiz elektron pochta manzili\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefon\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefon kerak\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Qo'llab-quvvatlash jamoasi uchun xabaringiz\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Suhbatni davom ettirishdan oldin <a href='https://tiledesk.com/termsofservice/' target='_blank'>shartlar</a> va <a href='https://tiledesk.com/privacy.html' target='_blank'>Maxfiylik siyosati</a>rozilik bildiring\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"Men roziman\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Ushbu qator to'ldirilishi shart\",\n            \n            \"TICKET_TAKING\": \"So'rov qabul qilindi va yordam xodimlari u bilan shug'ullanmoqda.\\nKo'proq izoh qo'shish uchun ushbu xatga javob yozing.\",\n            \n            \"LABEL_TODAY\": \"bugungi kunda\",\n            \"LABEL_TOMORROW\": \"kecha\",\n            \"LABEL_LAST_ACCESS\": \"oxirgi kirish\",\n            \"LABEL_TO\": \"da\",\n            \"ARRAY_DAYS\": [\"Dushanba\", \"Seshanba\", \"Chorshanba\", \"Payshanba\", \"Juma\", \"Shanba\", \"Yakshanba\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"ilova yubordi\",\n            \"SENT_AN_IMAGE\": \"rasm yubordi\",\n            \n            \"LABEL_PREVIEW\": \"Ko'rib chiqish\",\n            \"SWITCH_TO\": \"Yoki o'tish:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Maksimal ruxsat etilgan o'lcham {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Fayl maksimal ruxsat etilgan o'lchamdan oshadi\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    },\n    {\n        \"lang\":\"AZ\",\n        \"data\": \n        {\n            \"LABEL_PLACEHOLDER\": \"mesajınızı yazın..\",\n            \"LABEL_START_NW_CONV\": \"Yeni söhbət\",\n            \"LABEL_FIRST_MSG\": \"Probleminizi qısaca təsvir edin, agent sizinlə əlaqə saxlayacaq.\",\n            \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 Hazırda bütün operatorlar oflayndır. Hər halda probleminizi təsvir edə bilərsiniz. O, sizə ən qısa zamanda qayıdacaq dəstək komandasına təyin olunacaq.\",\n            \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Ofislərimiz bağlıdır. Hər halda probleminizi təsvir edə bilərsiniz. O, sizə ən qısa zamanda qayıdacaq dəstək komandasına təyin olunacaq.\",\n            \"LABEL_WHATSAPP\": \"Bizə mesaj göndərin\",\n            \"LABEL_SELECT_TOPIC\": \"Mövzu seçin\",\n            \"LABEL_COMPLETE_FORM\": \"Növbəti mövcud agentlə söhbətə başlamaq üçün formanı doldurun.\",\n            \"LABEL_FIELD_NAME\": \"ad\",\n            \"LABEL_ERROR_FIELD_NAME\": \"Tələb olunan sahə (minimum 5 simvol).\",\n            \"LABEL_FIELD_EMAIL\": \"E-poçt\",\n            \"LABEL_ERROR_FIELD_EMAIL\": \"Etibarlı e-poçt ünvanı daxil edin.\",\n            \"LABEL_ERROR_FIELD_REQUIRED\": \"tələb olunan sahə\",\n            \"LABEL_WRITING\": \"yazır...\",\n            \"LABEL_SEND_NEW_MESSAGE\": \"Yeni mesaj göndərin\",\n            \"AGENT_NOT_AVAILABLE\": \" Oflayn\",\n            \"AGENT_AVAILABLE\": \" Onlayn\",\n            \"GUEST_LABEL\": \"Qonaq\",\n            \"ALL_AGENTS_OFFLINE_LABEL\": \"Hazırda bütün operatorlar oflayndır\",\n            \"LABEL_LOADING\": \"Yüklənir...\",\n            \"CALLOUT_TITLE_PLACEHOLDER\": \"🖐 Kömək lazımdır?\",\n            \"CALLOUT_MSG_PLACEHOLDER\": \"Bura klikləyin və bizimlə söhbət etməyə başlayın!\",\n            \"CUSTOMER_SATISFACTION\": \"Müştəri məmnuniyyəti\",\n            \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"Müştəri xidmətlərimiz haqqında rəyiniz\",\n            \"DOWNLOAD_TRANSCRIPT\": \"Transkript yükləyin\",\n            \"BACK\": \"Geri\",\n            \"CONTINUE\": \"Davam et\",\n            \"YOUR_RATING\": \"sizin reytinqiniz\",\n            \"WRITE_YOUR_OPINION\": \"Fikrinizi yazın... (istəyə görə)\",\n            \"SUBMIT\": \"təqdim\",\n            \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Qiymətləndirməniz üçün təşəkkür edirik\",\n            \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"reytinqiniz alındı\",\n            \"ALERT_LEAVE_CHAT\": \"Çatı tərk etmək istəyirsiniz?\",\n            \"YES\": \"Bəli\",\n            \"NO\": \"Yox\",\n            \"BUTTON_CLOSE_TO_ICON\": \"Söhbəti minimuma endir\",\n            \"BUTTON_EDIT_PROFILE\": \"Profili yeniləyin\",\n            \"RATE_CHAT\": \"Söhbəti qiymətləndirin\",\n            \"WELCOME_TITLE\": \"Salam, Tiledesk 👋-a xoş gəlmisiniz\",\n            \"WELCOME_MSG\": \"Biz necə kömək edə bilərik?\",\n            \"WELCOME\": \"Xoş gəldiniz\",\n            \"OPTIONS\": \"seçimlər\",\n            \"SOUND_OFF\": \"Səsi kəsilir\",\n            \"SOUND_ON\": \"Səsi yandırın\",\n            \"LOGOUT\": \"Çıxış\",\n            \"CLOSE\": \"Yaxın\",\n            \"RESTART\":\"Yenidən başlamaq\",\n            \"PREV_CONVERSATIONS\": \"Söhbətləriniz\",\n            \"YOU\": \"Sən\",\n            \"SHOW_ALL_CONV\": \"hamısını göstər\",\n            \"START_A_CONVERSATION\": \"Söhbətə başlayın\",\n            \"NO_CONVERSATION\": \"Söhbət yoxdur\",\n            \"SEE_PREVIOUS\": \"əvvəlki baxın\",\n            \"WAITING_TIME_FOUND\": \"Komanda adətən $reply_timeilə cavab verir\",\n            \"WAITING_TIME_NOT_FOUND\": \"Komanda mümkün qədər tez cavab verəcəkdir\",\n            \"CLOSED\": \"Bağlı\",\n            \"CLOSE_CHAT\": \"Söhbəti bağlayın\",\n            \"MINIMIZE\":\"Minimallaşdırın\",\n            \"MAXIMIZE\":\"Maksimallaşdırın\",\n            \"CONFIRM_CLOSE_CHAT\":\"Bu söhbəti bağlamaq istədiyinizə əminsiniz?\",\n\n            \"INFO_SUPPORT_USER_ADDED_SUBJECT\": \"Sən\",\n            \"INFO_SUPPORT_USER_ADDED_YOU_VERB\": \"siz əlavə olundunuz \",\n            \"INFO_SUPPORT_USER_ADDED_COMPLEMENT\": \"söhbət\",\n            \"INFO_SUPPORT_USER_ADDED_VERB\": \"qoşuldu\",\n            \"INFO_SUPPORT_CHAT_REOPENED\": \"Çat yenidən açıldı\",\n            \"INFO_SUPPORT_CHAT_CLOSED\": \"Çat bağlandı\",\n            \"INFO_SUPPORT_LEAD_UPDATED\":\"Rəqəm yeniləndi\",\n            \"INFO_SUPPORT_MEMBER_LEFT_GROUP\":\"qrupdan çıxarıldı\",\n            \"INFO_SUPPORT_MEMBER_ABANDONED_GROUP\":\"söhbətləri tərk etdi\",\n            \"INFO_A_NEW_SUPPORT_REQUEST_HAS_BEEN_ASSIGNED_TO_YOU\": \"Sizə yeni dəstək sorğusu təyin edilib\",\n            \n            \"LABEL_PRECHAT_USER_FULLNAME\": \"Tam adı\",\n            \"LABEL_PRECHAT_USER_EMAIL\": \"E-poçt\",\n            \"LABEL_PRECHAT_USER_EMAIL_ERROR\": \"E-mail ünvanı səhvdir\",\n            \"LABEL_PRECHAT_USER_PHONE\": \"Telefon\",\n            \"LABEL_PRECHAT_USER_PHONE_ERROR\": \"Telefon tələb olunur\",\n            \"LABEL_PRECHAT_FIRST_MESSAGE\": \"Dəstək komandası üçün mesajınız\",\n            \"LABEL_PRECHAT_STATIC_TERMS_PRIVACY\": \"Söhbətə davam etməzdən əvvəl <a href='https://tiledesk.com/termsofservice/' target='_blank'>Şərt</a> və <a href='https://tiledesk.com/privacy.html' target='_blank'>Məxfilik Siyasəti</a>ilə razılaşın\",\n            \"LABEL_PRECHAT_ACCEPT_TERMS_PRIVACY\": \"razıyam\",\n            \"PRECHAT_REQUIRED_ERROR\": \"Bu xananın doldurulması məcburidir\",\n            \n            \"TICKET_TAKING\": \"Müraciət qəbul edilib və yardım işçiləri bununla məşğul olur.\\nDaha çox şərh əlavə etmək üçün bu e-poçta cavab yazın.\",\n            \n            \"LABEL_TODAY\": \"bu gün\",\n            \"LABEL_TOMORROW\": \"dünən\",\n            \"LABEL_LAST_ACCESS\": \"son giriş\",\n            \"LABEL_TO\": \"saat\",\n            \"ARRAY_DAYS\": [\"Bazar ertəsi, çərşənbə axşamı, çərşənbə, cümə axşamı, cümə, şənbə, bazar\"],\n            \n            \"SENT_AN_ATTACHMENT\": \"əlavə göndərdi\",\n            \"SENT_AN_IMAGE\": \"şəkil göndərdi\",\n            \n            \"LABEL_PREVIEW\": \"Önizləmə\",\n            \"SWITCH_TO\": \"Və ya keçin:\",\n\t\t\t\"MAX_ATTACHMENT\": \"Maksimal icazə verilən ölçü {{file_size_limit}}Mb\",\n\t\t\t\"MAX_ATTACHMENT_ERROR\": \"Fayl maksimal icazə verilən ölçünü aşır\",\n    \t\t\"EMOJI\": \"Emoji\"\n        }\n    }\n]\n    \n"
  },
  {
    "path": "config/widget.js",
    "content": "module.exports = {\n  'location':'http://localhost:4200/',\n  'testLocation':'http://localhost:4200/',\n};\n"
  },
  {
    "path": "config/winston-mt-multilogger.js",
    "content": "// UNUSED\nvar appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n\n// https://github.com/winstonjs/winston/issues/736\nvar loggers = {}\n\nfunction getLogger(moduleName) {\n  if (!loggers[moduleName]) {\n    loggers[moduleName] = createNewLogger(moduleName)\n  }\n\n  return loggers[moduleName]\n}\n\n\nvar level = process.env.LOG_LEVEL || 'info'\n// console.log(\"level\",level);\n\n\n\n\n  function createNewLogger(moduleName) {  \n\n    var options = {\n      file: {\n        level:level ,\n        filename: `${appRoot}/logs/${moduleName}.log`,\n        handleExceptions: true,\n        json: false,\n        maxsize: 5242880, // 5MB\n        maxFiles: 5,\n        colorize: false,\n        format: winston.format.simple()\n      }\n    };\n\n    let logger = winston.createLogger({    \n      transports: [\n      new (winston.transports.File)(options.file),\n      //new (winston.transports.MongoDB)( {db: logsDb, collection: \"logs\"}) \n      ],\n      exitOnError: false, // do not exit on handled exceptions\n    });\n   \n    return logger\n  }\n  \n  \n\n\n  // if (process.env.WRITE_LOG_TO_MONGODB==\"true\") {\n  //   require('winston-mongodb');\n\n\n  //   if (process.env.NODE_ENV == 'test')  {\n  //     var logsDb = config.databasetest;\n  //   }else {\n  //     var logsDb = config.database;\n  //   }\n \n\n  //   console.log(\"Added winston MongoDB transport\");\n  //   logger.add(new winston.transports.MongoDB({db: logsDb, collection: \"logsmt\"}));\n  // }\n    \n\n\n  // logger.stream = {\n  //   write: function(message, encoding) {\n  //     logger.info(message);\n  //   },\n  // };\n\n\n  module.exports = getLogger\n"
  },
  {
    "path": "config/winston-mt.js",
    "content": "// UNUSED\nvar appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n\nvar level = process.env.LOG_LEVEL || 'info'\n// console.log(\"level\",level);\n\nvar options = {\n    file: {\n      level:level ,\n      filename: `${appRoot}/logs/app.log`,\n      handleExceptions: true,\n      json: false,\n      maxsize: 5242880, // 5MB\n      maxFiles: 5,\n      colorize: false,\n      format: winston.format.simple()\n    }\n  };\n\n  let logger = winston.createLogger({    \n    transports: [\n     new (winston.transports.File)(options.file),\n    ],\n    exitOnError: false, // do not exit on handled exceptions\n  });\n\n\n\n\n  if (process.env.WRITE_LOG_MT_TO_MONGODB==\"true\") {\n    //require('winston-mongodb');\n    require('../utils/winston-mongodb/winston-mongodb');\n\n    if (process.env.NODE_ENV == 'test')  {\n      var logsDb = config.databasetest;\n    }else {\n      var logsDb = config.database;\n    }\n\n    console.log(\"Added winston MongoDB transport\");\n    logger.add(new winston.transports.MongoDB({db: logsDb, level:\"verbose\", collection: \"logsmt\"}));\n  }\n    \n\n\n  logger.stream = { \n    write: function(message, encoding) {\n      logger.info(message);\n    },\n  };\n\n  // if (process.env.NODE_ENV !== 'production') {\n  //   logger.add(new winston.transports.Console({\n  //     format: winston.format.simple()\n  //   }));\n  // }\n\n\n  module.exports = logger;\n"
  },
  {
    "path": "config/winston.js",
    "content": "var appRoot = require('app-root-path');\nvar winston = require('winston');\nvar config = require('./database');\n\nvar level = process.env.LOG_LEVEL || 'info'\n// console.log(\"level\",level);\n\nvar options = {\n    file: {\n      level:level ,\n      filename: `${appRoot}/logs/app.log`,\n      handleExceptions: true,\n      json: false,\n      maxsize: 5242880, // 5MB\n      maxFiles: 5,\n      colorize: false,\n      format: winston.format.simple()\n      // format: winston.format.combine( //https://github.com/winstonjs/winston#string-interpolation\n      //   winston.format.splat(),\n      //   winston.format.simple()\n      // ),\n    },\n    console: {\n      level: level,\n      handleExceptions: true,\n      json: true,\n      colorize: true,\n      // timestamp: true,\n      format: winston.format.simple() \n      // format: winston.format.combine(\n      //   winston.format.splat(),\n      //   winston.format.simple()\n      // ),\n    },\n  };\n\n//   https://stackoverflow.com/questions/51074805/typeerror-winston-logger-is-not-a-constructor-with-winston-and-morgan\n//   var logger = new winston.Logger({\n//     transports: [\n//       new winston.transports.File(options.file),\n//       new winston.transports.Console(options.console)\n//     ],\n//     exitOnError: false, // do not exit on handled exceptions\n//   });\n\n\n\n  let logger = winston.createLogger({    \n    transports: [\n     new (winston.transports.Console)(options.console),\n     new (winston.transports.File)(options.file),\n     //new (winston.transports.MongoDB)( {db: logsDb, collection: \"logs\"}) \n    ],\n    exitOnError: false, // do not exit on handled exceptions\n  });\n\n\n\n\n  if (process.env.WRITE_LOG_TO_MONGODB==\"true\") {\n    //require('winston-mongodb');\n    require('../utils/winston-mongodb/winston-mongodb');\n\n    if (process.env.NODE_ENV == 'test')  {\n      var logsDb = process.env.DATABASE_LOG_URI || process.env.DATABASE_URI || process.env.MONGODB_URI || config.databasetest;\n    }else {\n      var logsDb = process.env.DATABASE_LOG_URI ||  process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\n    }\n\n    console.log(\"Added winston MongoDB transport\");\n    logger.add(new winston.transports.MongoDB({db: logsDb, level: process.env.LOG_MONGODB_LEVEL || 'info', collection: \"logs\"}));\n  }\n    \n  if (process.env.WRITE_LOG_MT_TO_MONGODB==\"true\") {\n    //require('winston-mongodb');\n    require('../utils/winston-mongodb/winston-mongodb');\n\n    if (process.env.NODE_ENV == 'test')  {\n      var logsDb = process.env.DATABASE_LOG_MT_URI || process.env.DATABASE_LOG_URI ||  process.env.DATABASE_URI || process.env.MONGODB_URI || config.databasetest;\n    }else {\n      var logsDb = process.env.DATABASE_LOG_MT_URI || process.env.DATABASE_LOG_URI ||  process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\n    }\n\n    console.log(\"Added winston MongoDB MT transport\");\n    logger.add(new winston.transports.MongoDB({db: logsDb, labelRequired:true,  level: process.env.LOG_MT_MONGODB_LEVEL || 'verbose', collection: \"logsmt\"}));\n  } \n\n\n  logger.stream = { \n    write: function(message, encoding) {\n      logger.info(message);\n    },\n  };\n\n  // if (process.env.NODE_ENV !== 'production') {\n  //   logger.add(new winston.transports.Console({\n  //     format: winston.format.simple()\n  //   }));\n  // }\n\n\n  module.exports = logger;\n"
  },
  {
    "path": "deploy-beta.sh",
    "content": "npm version prerelease --preid=beta\nversion=`node -e 'console.log(require(\"./package.json\").version)'`\necho \"version $version\"\n\nif [ \"$version\" != \"\" ]; then\n    git tag -a \"$version\" -m \"`git log -1 --format=%s`\"\n    echo \"Created a new tag, $version\"\n    git push --tags\n    npm publish --access public\nfi\ngit push"
  },
  {
    "path": "deploy.sh",
    "content": "git pull\nnpm version patch\nversion=`node -e 'console.log(require(\"./package.json\").version)'`\necho \"version $version\"\n\nif [ \"$version\" != \"\" ]; then\n    git tag -a \"$version\" -m \"`git log -1 --format=%s`\"\n    echo \"Created a new tag, $version\"\n    git push --tags\n    npm publish --access public\nfi\ngit push"
  },
  {
    "path": "deploynew.sh",
    "content": "#!/bin/bash\n\n# Load .env variables\nif [ -f .env ]; then\n  export $(grep -v '^#' .env | xargs)\nfi\n\n# Check if the token is set\nif [ -z \"$NPM_PUBLISH_TOKEN\" ]; then\n  echo \"⚠️ Missing NPM_PUBLISH_TOKEN in environment.\"\n  echo \"You can speed up the process by setting the environment variable with your publish token.\"\n  read -p \"Do you want to continue with manual login? (y/n): \" choice\n  if [[ \"$choice\" != \"y\" && \"$choice\" != \"Y\" ]]; then\n    echo \"❌ Deploy aborted.\"\n    exit 1\n  else\n    echo \"💡 Proceed with 'npm login' manually...\"\n    npm login\n  fi\nelse\n  # Create temporary .npmrc with the token\n  echo \"//registry.npmjs.org/:_authToken=${NPM_PUBLISH_TOKEN}\" > ~/.npmrc\nfi\n\ngit pull\n\n# Determine version bump type (default: patch)\nVERSION_TYPE=\"${1:-patch}\"\n\n# Validate version type\nif [[ \"$VERSION_TYPE\" != \"patch\" && \"$VERSION_TYPE\" != \"minor\" && \"$VERSION_TYPE\" != \"major\" ]]; then\n  echo \"❌ Invalid version type: $VERSION_TYPE\"\n  echo \"Usage: ./deploynew.sh [patch|minor|major]\"\n  echo \"Default: patch (if no argument provided)\"\n  exit 1\nfi\n\necho \"📦 Bumping version: $VERSION_TYPE\"\nnpm version $VERSION_TYPE\nversion=`node -e 'console.log(require(\"./package.json\").version)'`\necho \"version $version\"\n\nif [ \"$version\" != \"\" ]; then\n    git tag -a \"$version\" -m \"`git log -1 --format=%s`\"\n    echo \"Created a new tag, $version\"\n    git push --tags\n    npm publish --access public\nfi\ngit push\n\n# Remove temporary .npmrc if created\nif [ -f ~/.npmrc ]; then\n  rm ~/.npmrc\nfi\n\necho \"🎉 Deploy completed!\""
  },
  {
    "path": "docs/api-dev.md",
    "content": "# TILEDESK LOCALHOST REST API\n\n## Signup\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"firstname\":\"Andrew\", \"lastname\":\"Lee\", \"email\":\"andrea.leo@f21.it\",\"password\":\"123456\"}' http://localhost:3000/auth/signup\n```\n\n\n## Signin\n\n```\ncurl -v -X POST -d 'email=andrea.leo@f21.it&password=123456' http://localhost:3000/auth/signin\n\ncurl -v -X POST -d 'email=andrea.leo@frontiere21.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signin\n```\n\n\n## Signin anonymously\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"firstname\":\"Andrew\", \"lastname\":\"Lee\", \"id_project\":\"123\"}' http://localhost:3000/auth/signinAnonymously\n```\n\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"id_project\":\"5e28108c361fbb001729e960\"}' https://tiledesk-server-pre.herokuapp.com/auth/signinAnonymously\n\n\n## Signin custom token\n\n\n{\n  \"_id\": \"123456\",\n  \"firstname\": \"andrea custom\",\n  \"lastname\": \"leo custom\",\n  \"email\": \"email2@email.com\",\n  \"custom1\": \"val1\",\n  \"attributes\": {\"c1\":\"v1\"},\n  \"sub\": \"userexternal\",\n  \"aud\": \"https://tiledesk.com/projects/5e28108c361fbb001729e960\"\n}\n\n\ncustom project secret: 4fa91e0b-bd9a-4025-b672-a5377edb70d9\n\ngenerato su https://jwt.io/\n\nhttps://jwt.io/\n\neyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIxMjM0NTYiLCJmaXJzdG5hbWUiOiJhbmRyZWEgY3VzdG9tIiwibGFzdG5hbWUiOiJsZW8gY3VzdG9tIiwiZW1haWwiOiJlbWFpbDJAZW1haWwuY29tIiwiY3VzdG9tMSI6InZhbDEiLCJhdHRyaWJ1dGVzIjp7ImMxIjoidjEifSwic3ViIjoidXNlcmV4dGVybmFsIiwiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20vcHJvamVjdHMvNWUyODEwOGMzNjFmYmIwMDE3MjllOTYwIn0.bkTwyedGSDSKcJan0flhXRk6fvPU31BiFQpqaJT9UGU\n\n\ncurl -v -X POST -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIxMjM0NTYiLCJmaXJzdG5hbWUiOiJhbmRyZWEgY3VzdG9tIiwibGFzdG5hbWUiOiJsZW8gY3VzdG9tIiwiZW1haWwiOiJlbWFpbDJAZW1haWwuY29tIiwiY3VzdG9tMSI6InZhbDEiLCJhdHRyaWJ1dGVzIjp7ImMxIjoidjEifSwic3ViIjoidXNlcmV4dGVybmFsIiwiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20vcHJvamVjdHMvNWUyODEwOGMzNjFmYmIwMDE3MjllOTYwIn0.bkTwyedGSDSKcJan0flhXRk6fvPU31BiFQpqaJT9UGU\" \\\n https://tiledesk-server-pre.herokuapp.com/auth/signinWithCustomToken\n\n\n\n\n\n\n## Firebase signin\n\n```\ncurl -v -X POST -d 'email=andrea.leo@f21.it&password=123456' http://localhost:3000/firebase/auth/signin\n```\n\n## Firebase createtoken\n\n```\ncurl -v -X POST -u andrea.leo@f21.it:123456 http://localhost:3000/firebase/createtoken\n```\n\n## Projects\n\n### Create\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\n```\n\n\n\n\n## Messages \n\n\n\n### Create \n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"sender\":\"io\", \"sender_fullname\":\"Andrea Leo\", \"text\":\"firstText\"}' http://localhost:3000/5ea800091147f28c72b90c5e/requests/req123456999/messages\n```\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e80c27549605db3eff5be3a/requests/\n\ncurl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"rating\":2, \"rating_message\":\"Andrea Leo\"}' http://localhost:3000/5e80c27549605db3eff5be3a/requests/req123456999/rating\n\n\ncurl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"rating\":2, \"rating_message\":\"Andrea Leo\"}' https://tiledesk-server-pre.herokuapp.com/5e8f56764aef0900178113b5/requests/support-group-M4VA5Eqx38lGRmZWMuB/rating\n\n\n\ncurl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"rating\":2, \"rating_message\":\"Andrea Leo\"}' https://tiledesk-server-pre.herokuapp.com/5e8f56764aef0900178113b5/requests/support-group-M4VA5Eqx38lGRmZWMuB/rating\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"firstText22\"}' https://tiledesk-server-pre.herokuapp.com/5df2240cecd41b00173a06bb/requests/support-group-5544/messages\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u 5fa26a59-6944-43eb-852a-36850086c357@tiledesk.com:a7de28c6-d309-4539-9749-43dd4535fa7c -d '{\"text\":\"firstText22\"}' https://tiledesk-server-pre.herokuapp.com/5df2240cecd41b00173a06bb/requests/support-group-554477991/messages\n\n\ncon anonym user\ncurl -v -X POST -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIwYzI2YmY2Mi1iM2RmLTQ1N2EtYjk1OS0zMGQzNjRiYTA2ZjEiLCJmaXJzdG5hbWUiOiJHdWVzdCIsImlkIjoiMGMyNmJmNjItYjNkZi00NTdhLWI5NTktMzBkMzY0YmEwNmYxIiwiZnVsbE5hbWUiOiJHdWVzdCAiLCJpYXQiOjE1Nzk2ODQ2MDcsImF1ZCI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwiaXNzIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJzdWIiOiJndWVzdCJ9.iDD_L35WsFI_gq3GXtJets5zjpZswbn4qdrv-kMgZu8\" \\\n -d '{\"text\":\"firstTextAnon\"}' https://tiledesk-server-pre.herokuapp.com/5e28108c361fbb001729e960/requests/support-group-55447799177/messages\n\ncon ct user:\n\ncurl -v -X POST -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiIxMjM0NTYiLCJmaXJzdG5hbWUiOiJhbmRyZWEgY3VzdG9tIiwibGFzdG5hbWUiOiJsZW8gY3VzdG9tIiwiZW1haWwiOiJlbWFpbDJAZW1haWwuY29tIiwiY3VzdG9tMSI6InZhbDEiLCJhdHRyaWJ1dGVzIjp7ImMxIjoidjEifSwic3ViIjoidXNlcmV4dGVybmFsIiwiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20vcHJvamVjdHMvNWUyODEwOGMzNjFmYmIwMDE3MjllOTYwIn0.bkTwyedGSDSKcJan0flhXRk6fvPU31BiFQpqaJT9UGU\" \\\n -d '{\"text\":\"firstTextCT\"}' https://tiledesk-server-pre.herokuapp.com/5e28108c361fbb001729e960/requests/support-group-5544779917789/messages\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"firstText22\"}' https://tiledesk-server-pre.herokuapp.com/5df2240cecd41b00173a06bb/requests/support-group-554477991/messages\n\n=====\n\ncon agente ==\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"hello from api\"}' https://tiledesk-server-pre.herokuapp.com/5e2aba4cb4c9f80017d50907/requests/tyuiop112/messages\n\n\ncon anonimo\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"id_project\":\"5e2aba4cb4c9f80017d50907\", \"firstname\":\"John\"}' https://tiledesk-server-pre.herokuapp.com/auth/signinAnonymously\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiJkNWYwZjZiOS1iYWM5LTQ5NGEtOWVmMy03YjBjZTc0OGM2MTkiLCJmaXJzdG5hbWUiOiJKb2huIiwiaWQiOiJkNWYwZjZiOS1iYWM5LTQ5NGEtOWVmMy03YjBjZTc0OGM2MTkiLCJmdWxsTmFtZSI6IkpvaG4gIiwiaWF0IjoxNTc5OTU2OTU1LCJhdWQiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsImlzcyI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwic3ViIjoiZ3Vlc3QifQ.ISnrH7rixeQoNLdrdFVIIBTJsJkAtkaIMYSn_SBR8L0\" \\\n -d '{\"text\":\"hello my name is John and I need help\"}' https://tiledesk-server-pre.herokuapp.com/5e2c35c8f0dbc10017bb3aac/requests/support-group-27df7cbf-3946-4ca4-9b17-dc16114108f10/messages\n\n\n\n\n\n\n\n\n### Get\n```\nsmessages/5beeb3835d34344cd4962a8c\n```\n\n\n\n\n## Requests \n\n### Create \n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{ \"text\":\"text\"}' http://localhost:4000/5ca366fdee19dbc72e98e96f/requests\n```\n\n\n### List\n\n```\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5bedbbd18b9ed53a6a3f3dd3/requests/req123456/\n```\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 https://tiledesk-server-pre.herokuapp.com/5df26badde7e1c001743b63c/requests/?limit=10\n\n### Get\n\n```\n\n### List\n\n```\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5ab0f32757066e0014bfd718/requests/\n```\n\n\n### Patch \n\n```\ncurl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"rating\":5, \"rating_message\":\"Great\"}' http://localhost:3000/5ab0f32757066e0014bfd718/requests/5b800a7f52ee93a525ca0d8c\n```\n\n# Add note\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"note1\"}' http://localhost:3000/5e5f6f7d791b4bc5a1c0b7b5/requests/req123456/notes\n```\n\n# delete note\n```\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e5f6f7d791b4bc5a1c0b7b5/requests/req123456/notes/5e5f719f791b4bc5a1c0b7c1\n```\n\n### Share by email\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5bc7678f3cbc8921530fffbb/requests/request_id-waitingTimeRequest/share/email?to=andrea.leo@f21.it\n```\n\n## Departments \n\n### Create \n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testdepartment\",\"id_bot\":\"idbot\"}' http://localhost:3000/5ab0f32757066e0014bfd718/departments\n```\n\n### List\n\n```\ncurl -v -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5ab0f32757066e0014bfd718/departments\n```\n\n### Create a default department\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"default\",\"id_bot\":\"\",\"default\":true}' http://localhost:3000/5ab0f32757066e0014bfd718/departments\n```\n\n### Get default department\n\n```\ncurl -v -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5ab0f32757066e0014bfd718/departments/default\n```\n\n### Get the available operator for a specific department\n```\ncurl -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5ad706aa7009f70267089951/departments/5ad706db7009f70267089955/operators\n```\n\n## Bots \n\n### List\n\n```\ncurl -v -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5ab0f32757066e0014bfd718/faq_kb\n```\n\n#### Create\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testbot\"}' http://localhost:3000/5bedbbd18b9ed53a6a3f3dd3/faq_kb\n```\n\n\n### ASK\n\n```\ncurl -v -X POST  -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"question\":\"test\",\"doctype\":\"normal\",\"min_score\":\"0.0\",\"remote_faqkb_key\":\"c9970cc1-a211-4390-b7d0-cdf154d464a9\"}' http://localhost:3000/5bedbbd18b9ed53a6a3f3dd3/faq/askbot\n```\n\n\n## WebHook Subscription\n\n### Create\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"message.create\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' http://localhost:3000/5bedbbd18b9ed53a6a3f3dd3/subscriptions\n```\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5bedbbd18b9ed53a6a3f3dd3/events\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -d '{\"id_project\":\"5e37f45c4d82de00178b96ad\"}' https://tiledesk-server-pre.herokuapp.com/auth/signinAnonymously\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"e1\", \"attributes\": {\"attr1\":\"val1\"}}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/events\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiJiNmFiNTFlMS05NWQ0LTQ3YTAtYjg2ZC0xMWU4OGY2MWMxMmMiLCJmaXJzdG5hbWUiOiJHdWVzdCIsImlkIjoiYjZhYjUxZTEtOTVkNC00N2EwLWI4NmQtMTFlODhmNjFjMTJjIiwiZnVsbE5hbWUiOiJHdWVzdCAiLCJpYXQiOjE1ODA3MjkxNjIsImF1ZCI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwiaXNzIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJzdWIiOiJndWVzdCJ9.0Xv0461DP7LLaJ9l1dMiWXlF7f69LBeofsrngvgQTIQ\" \\\n  -d '{\"name\":\"e1\", \"attributes\": {\"attr1\":\"val1\"}}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/events\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"e1\",\"attributes\": {\"id_user\":\"mBmETIl7uWPn5L2LzUTlxQO6re62\", \"fullname\":\"uccio\",\"email\":\"4@4.it\",\"attr1\":\"val1\"}}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/events\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"firstText22\"}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/requests/support-group-2dd08a5d-73b3-4aed-bb58-7580fef2d03c/messages\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"message.create\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/subscriptions\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"request.create\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/subscriptions\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"request.update\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/subscriptions\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"request.create\", \"target\":\"https://webservice.claybox.dc.pype.engineering/v3/tiledesk/message\"}' https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/subscriptions\n\n\n\n\n\n\ncurl -v -X GET -u andrea.leo@f21.it:123456  https://tiledesk-server-pre.herokuapp.com/5e37f45c4d82de00178b96ad/subscriptions/history\n\ncurl -v -X GET -u andrea.leo@f21.it:XXXXX https://tiledesk-server-pre.herokuapp.com/<PROJECT_ID>/subscriptions/history\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"project_user.invite\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"project_user.update\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"project_user.delete\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"department.create\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"department.update\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"department.delete\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"group.create\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"group.update\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"group.delete\", \"target\":\"https://tiledesk.requestcatcher.com/test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/subscriptions\n\n\n\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' \\\n -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1YWIxMWM2YjgzZGMyNDAwMTRkNDYwOTUiLCJlbWFpbCI6ImFuZHJlYS5sZW9AZjIxLml0IiwiZmlyc3RuYW1lIjoiYW5kcmVhIiwiaWF0IjoxNTgxMTgzNDk4LCJhdWQiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsImlzcyI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwic3ViIjoidXNlciIsImp0aSI6IjMwMDQ5YWRmLWY5NzEtNGI2Ni05Y2JhLTMwMmQwMGNhZmVjMSJ9.o63BsK4_mSNnS_dp5T2jbJgvy_GhyN81zO8Gpvx8sIM\" \\\n   http://localhost:3000/5e3e9f51b44414f76a4c5c22/events\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/jwt/history\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/jwt/history/4691a1e0-9b25-4c81-a1f2-b63b8ba8fec6\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"user_available\":false}' http://localhost:3000/5e3e9f51b44414f76a4c5c22/project_users/\n\n\n\ncurl -v -X GET  -u andrea.leo@f21.it:123456 http://localhost:3000/projects/\n\n\n\n\n# Canned\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"test\"}' http://localhost:3000/5e54f53bb1d39805a52042cd/canned/\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:123456 -d '{\"text\":\"test\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/canned/\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/canned/\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:123456 https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/canned/\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"test2\"}' http://localhost:3000/5e54f53bb1d39805a52042cd/canned/5e54f545b1d39805a52042d1\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:123456 -d '{\"text\":\"test2\"}' https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/canned/5e54f89bf2b494001723c53c\n\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/canned/5e54f545b1d39805a52042d1\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:123456  https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/canned/5e54f89bf2b494001723c53c\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/canned/5e54f545b1d39805a52042d1/physical\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:123456  https://tiledesk-server-pre.herokuapp.com/5e20a68e7c2e640017f2f40f/canned/5e54f89bf2b494001723c53c/physical\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n# Tags\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"tag\":\"test\"}' http://localhost:3000/5e54f53bb1d39805a52042cd/tags/\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/tags/5e5e9b13919c7c51078cf0b5\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/tags/\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"tag\":\"test2\"}' http://localhost:3000/5e54f53bb1d39805a52042cd/tags/5e5e9b13919c7c51078cf0b5\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456  http://localhost:3000/5e54f53bb1d39805a52042cd/tags/5e5e9b13919c7c51078cf0b5\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"event\":\"request.create\", \"target\":\"https://tiledesk-queues.herokuapp.com/webhook\"}' https://tiledesk-server-pre.herokuapp.com/5e4d0478fa669b0017c96817/subscriptions\n\n\ncurl -v -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5e5fb940c397af3d5aeee56e/requests\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"sender\":\"io\", \"sender_fullname\":\"Andrea Leo\", \"text\":\"firstText\"}' http://localhost:3000/5e5fb940c397af3d5aeee56e/requests/req123456/messages\n\n\n\ncurl -v -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5e5fb940c397af3d5aeee56e/departments/5e5fb941c397af3d5aeee570/operators\n\n\n\ncurl -v -X GET  -u andrea.leo@frontiere21.it:258456 https://tiledesk-server-pre.herokuapp.com/5e4d0478fa669b0017c96817/departments/5e4ebae1c84c6200170a98f4/operators\n\n\ncurl -v -X GET -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXQiOiI2NzhlNTI4ZC05ODk2LTQ2NzEtODhmNC05M2Q3MDcwZTEzOTQiLCJfaWQiOiI1ZTVkNGU0Y2JkMGE5YjAwMTc5ZmYzZTAiLCJ0YXJnZXQiOiJodHRwOi8vdGlsZWRlc2stcXVldWVzLmhlcm9rdWFwcC5jb20vd2ViaG9vayIsImV2ZW50IjoicmVxdWVzdC5jcmVhdGUiLCJpZF9wcm9qZWN0IjoiNWU1ZDQwYjJiZDBhOWIwMDE3OWZmM2NkIiwiY3JlYXRlZEJ5IjoiNWUwOWQxNmQ0ZDM2MTEwMDE3NTA2ZDdmIiwiY3JlYXRlZEF0IjoiMjAyMC0wMy0wMlQxODoxOTo1Ni44NjZaIiwidXBkYXRlZEF0IjoiMjAyMC0wMy0wMlQxODoxOTo1Ni44NjZaIiwiX192IjowLCJpYXQiOjE1ODM0MzMxNTcsImF1ZCI6Imh0dHBzOi8vdGlsZWRlc2suY29tL3N1YnNjcmlwdGlvbnMvNWU1ZDRlNGNiZDBhOWIwMDE3OWZmM2UwIiwiaXNzIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJzdWIiOiJzdWJzY3JpcHRpb24ifQ.L9vGCNlbdkIYmYVIyHDnfmPwLWgn8VHoodVUCnqNN9g\" \\\n https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/departments/5e5d40b2bd0a9b00179ff3cf/operators\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"event\":\"request.create\", \"target\":\"https://tiledesk-queues.herokuapp.com/webhook\"}' https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/subscriptions\n\n\ncurl -v -X GET -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXQiOiJjZmUyNDVhNS03ZmYyLTQ3ZDItYjYxMS0yYTMzYmNmNDMxOWMiLCJfaWQiOiI1ZTY1MDc5M2M3YTU5MzAwMTczNGY2OTEiLCJ0YXJnZXQiOiJodHRwczovL3dlYmhvb2suc2l0ZS84NTNlNGU1ZS0xOGY4LTQ1ZTYtYTM2Ni1kYTBjNjM0NzBlZDYiLCJldmVudCI6InJlcXVlc3QuY3JlYXRlIiwiaWRfcHJvamVjdCI6IjVlNWQ0MGIyYmQwYTliMDAxNzlmZjNjZCIsImNyZWF0ZWRCeSI6IjVhYWE5OTAyNGMzYjExMDAxNGI0NzhmMCIsImNyZWF0ZWRBdCI6IjIwMjAtMDMtMDhUMTQ6NTY6MTkuMDQwWiIsInVwZGF0ZWRBdCI6IjIwMjAtMDMtMDhUMTQ6NTY6MTkuMDQwWiIsIl9fdiI6MCwiaWF0IjoxNTgzNjc5Mzk1LCJhdWQiOiJodHRwczovL3RpbGVkZXNrLmNvbS9zdWJzY3JpcHRpb25zLzVlNjUwNzkzYzdhNTkzMDAxNzM0ZjY5MSIsImlzcyI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwic3ViIjoic3Vic2NyaXB0aW9uIn0.b0A125LkxUaXYLqJhzR5uEXtKF_KuXd9l4dET4jPgpc\" https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/requests\n\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e667419003835ec4a690e05/project_users/\n\ncurl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"user_available\":false}' http://localhost:3000/5e667419003835ec4a690e05/project_users/\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"member\":\"5aaa99024c3b110014b478f0\"}' https://tiledesk-server-pre.herokuapp.com/5e667981aa70910017b915a2/requests/support-group-3de2cbb9-d061-4388-b3e0-f51ccadd1e9b/participants/\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '[]' https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/requests/support-group-3de2cbb9-d061-4388-b3e0-f51ccadd1e9b/participants/\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"event\":\"request.create\", \"target\":\"https://webhook.site/853e4e5e-18f8-45e6-a366-da0c63470ed6\"}' https://tiledesk-server-pre.herokuapp.com/5e667981aa70910017b915a2/subscriptions\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/requests?limit=1\n\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"max_served_chat\":199}' https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/project_users/\n\n\n\n\n\ncurl -v -X GET -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZTY3NTI1NTdiM2I3MzAwMTcxNDViN2YiLCJ0YXJnZXQiOiJodHRwczovL3dlYmhvb2suc2l0ZS84NTNlNGU1ZS0xOGY4LTQ1ZTYtYTM2Ni1kYTBjNjM0NzBlZDYiLCJldmVudCI6InJlcXVlc3QuY3JlYXRlIiwiaWRfcHJvamVjdCI6IioiLCJjcmVhdGVkQnkiOiI1YWFhOTkwMjRjM2IxMTAwMTRiNDc4ZjAiLCJjcmVhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJ1cGRhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJfX3YiOjAsImlhdCI6MTU4Mzg2MzQyNywiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJpc3MiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsInN1YiI6InN1YnNjcmlwdGlvbiJ9.vw9uQGtuhEwkMy3_BM73R0cx6vGJH0giOeTvNd8CBTk\" https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/requests\n\n\n\ncurl -v -X GET -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZTY3NTI1NTdiM2I3MzAwMTcxNDViN2YiLCJ0YXJnZXQiOiJodHRwczovL3dlYmhvb2suc2l0ZS84NTNlNGU1ZS0xOGY4LTQ1ZTYtYTM2Ni1kYTBjNjM0NzBlZDYiLCJldmVudCI6InJlcXVlc3QuY3JlYXRlIiwiaWRfcHJvamVjdCI6IioiLCJjcmVhdGVkQnkiOiI1YWFhOTkwMjRjM2IxMTAwMTRiNDc4ZjAiLCJjcmVhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJ1cGRhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJfX3YiOjAsImlhdCI6MTU4Mzg2MzQyNywiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJpc3MiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsInN1YiI6InN1YnNjcmlwdGlvbiJ9.vw9uQGtuhEwkMy3_BM73R0cx6vGJH0giOeTvNd8CBTk\" http://localhost:3000/5e667419003835ec4a690e05/leads\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e68b20a990b7729583d463b/project_users/\n\ncurl -v -X PUT -H 'Content-Type:application/json' -H \"Authorization: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZTY3NTI1NTdiM2I3MzAwMTcxNDViN2YiLCJ0YXJnZXQiOiJodHRwczovL3dlYmhvb2suc2l0ZS84NTNlNGU1ZS0xOGY4LTQ1ZTYtYTM2Ni1kYTBjNjM0NzBlZDYiLCJldmVudCI6InJlcXVlc3QuY3JlYXRlIiwiaWRfcHJvamVjdCI6IioiLCJjcmVhdGVkQnkiOiI1YWFhOTkwMjRjM2IxMTAwMTRiNDc4ZjAiLCJjcmVhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJ1cGRhdGVkQXQiOiIyMDIwLTAzLTEwVDA4OjM5OjQ5Ljc3NloiLCJfX3YiOjAsImlhdCI6MTU4Mzg2MzQyNywiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJpc3MiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsInN1YiI6InN1YnNjcmlwdGlvbiJ9.vw9uQGtuhEwkMy3_BM73R0cx6vGJH0giOeTvNd8CBTk\" -d '{\"user_available\":true}' http://localhost:3000/5e68b20a990b7729583d463b/project_users/5e68b20a990b7729583d463c\n\n\n\n\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '[]' https://tiledesk-server-pre.herokuapp.com/5e5d40b2bd0a9b00179ff3cd/requests/support-group-340edb31-aab1-465d-921f-8c776abf7bca/participants\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e77d1a339d2034ca6d3244e/requests/req123456999/history\n\n\n\n\n\n\n\n\ncurl -v -X POST -d 'email=andrea.leo@f23.it&password=123456' http://localhost:3000/auth/signup\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f23.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\ncurl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f23.it:123456 http://localhost:3000/users/\n\n\n\n\ndb.users.update(\n  {},\n  { $set: {\"status\": 100} },\n  false,\n  true\n)\n\n\ndb.projects.update(\n  {},\n  { $set: {\"status\": 100} },\n  false,\n  true\n)\n\n\n\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u cristina@cristina.it https://tiledesk-server-pre.herokuapp.com/users/\n\n\ncurl -v -X POST -d 'email=andrea.leo@f21.it&password=123456' http://localhost:3000/auth/signup\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e7a3a6a3ad6f8cd4fb19474/events\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"e1\", \"attributes\": {\"attr1\":\"val1\"}}' http://localhost:3000/5e7a3a6a3ad6f8cd4fb19474/events\n\ncurl -X GET -u andrea.leo@f21.it:123456 http://localhost:3000/5e79e725ecb9230ac1f5b4a2/departments/5e79e725ecb9230ac1f5b4a4/operators\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"event\":\"event.emit.typing.start\", \"target\":\"https://webhook.site/fae496ca-c0a5-4eff-b9aa-53c01585150a\"}' https://tiledesk-server-pre.herokuapp.com/5e708a2dd6f080001763928c/subscriptions\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"event\":\"event.emit.typing.start\", \"target\":\"https://webhook.site/fae496ca-c0a5-4eff-b9aa-53c01585150a\"}' http://localhost:3000/5e7a3a6a3ad6f8cd4fb19474/subscriptions\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{ \"text\":\"text\"}' http://localhost:3000/5e79e725ecb9230ac1f5b4a2/requests\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{ \"text\":\"text1\"}' https://tiledesk-server-pre.herokuapp.com/5e7b6135cb50cc00178a1eba/requests\n\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 https://tiledesk-server-pre.herokuapp.com/5e6fabbad6f080001763891d/requests/support-group-33e3fe3e-7e26-47c9-9a51-d70f94489e65/history\n\n\n\n\n\n\n\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"text\":\"firstText\"}' http://localhost:3000/5ea800091147f28c72b90c5e/requests\n```\n\n\n```\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\n\n```\n\n\n\ncurl -v -X POST -d 'email=andrea.leo@f21.it&password=123456' http://192.168.99.100:30269/auth/signup\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://192.168.99.100:30269/projects\n\n\n\n\n\n\n\ncurl -v -X POST -d 'email=w1@w1.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w2@w2.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w3@w3.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w4@w4.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w5@w5.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w6@w6.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w7@w7.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w8@w8.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w9@w9.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w10@w10.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w11@w11.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w12@w12.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w1@w1.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w2@w2.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w3@w3.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w4@w4.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w5@w5.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w6@w6.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w7@w7.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w8@w8.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w9@w9.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w10@w10.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w11@w11.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w12@w12.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w131@w131.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\ncurl -v -X POST -d 'email=w13@w13.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w14@w14.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w15@w15.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w16@w16.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w17@w17.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w18@w18.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w19@w19.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\ncurl -v -X POST -d 'email=w20@w20.it&password=123456' https://tiledesk-server-pre.herokuapp.com/auth/signup\n\ncurl -v -X POST -d 'email=w201@w201.it&password=123456&firstname=a&lastname=b' https://tiledesk-server-pre.herokuapp.com/auth/signup\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w14@w14.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w15@w15.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w16@w16.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w17@w17.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w18@w18.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w19@w19.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"w20@w20.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/project_users/invite\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"email\":\"andrea@w20.it\",\"role\":\"agent\",\"user_available\":true}' https://tiledesk-server-pre.herokuapp.com/5df26badde7e1c001743b63c/project_users/invite\n\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText1\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText2\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText3\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText4\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText5\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText6\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText7\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText8\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText9\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText10\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText11\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText12\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText13\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText14\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText15\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText16\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText17\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText18\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText19\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText20\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\n\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText1\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText2\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText3\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText4\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText5\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText6\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText7\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText8\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText9\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText10\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText11\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText12\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText13\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText14\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText15\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText16\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText17\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText18\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText19\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText20\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\n\n\n\n\nws count 23 2020-05-05T10:17:45.939Z 2020-05-05T10:17:46.674Z\n\n\n {\"id_project\": \"5eb1116b4ec80100176671d2\",\"status\": { \"$lt\": 1000 },\n \"$or\":[ { \"agents.id_user\": \"5eb07ab54ec8010017666523\" },  { \"participants\": \"5eb07ab54ec8010017666523\" } ] }\n  23 \n\n\n\n\n  db.getCollection('requests').find( {\"id_project\": \"5eb1116b4ec80100176671d2\",\"status\": { \"$lt\": 1000 }, \"$or\":[ { \"agents.id_user\": ObjectId(\"5eb116f54ec8010017667718\") }, {\"participants\": \"5eb116f54ec8010017667718\" }] })\n\n\n    db.getCollection('requests').find( {\"id_project\": \"5eb1116b4ec80100176671d2\",\"status\": { \"$lt\": 1000 } })\n\n\ndb.getCollection('requests').find( {\"id_project\": \"5eb1116b4ec80100176671d2\",\"status\": { \"$lt\": 1000 } }).sort( { \"updatedAt\": -1 } )\n\n\n  db.getCollection('requests').find( {\"id_project\": \"5eb1116b4ec80100176671d2\",\"status\": { \"$lt\": 1000 }, \"$or\":[ { \"agents.id_user\": ObjectId(\"5eb116f54ec8010017667718\") }, {\"participants\": \"5eb116f54ec8010017667718\" }] }).sort( { \"updatedAt\": -1 } )\n\n\n\n\n\n\n\n\n ws count { id_project: '5eb1116b4ec80100176671d2',\n2020-05-05T10:37:00.500941+00:00 app[web.1]:   status: { '$lt': 1000 },\n2020-05-05T10:37:00.500943+00:00 app[web.1]:   '$or': [ { preflight: false }, { preflight: [Object] } ] } 23 2020-05-05T10:36:59.889Z 2020-05-05T10:37:00.500Z\n\n\n\n\n\n2020-05-05T11:26:45.800050+00:00 app[web.1]: ws count { id_project: '5eb1116b4ec80100176671d2',\n2020-05-05T11:26:45.800060+00:00 app[web.1]:   status: { '$lt': 1000 },\n2020-05-05T11:26:45.800061+00:00 app[web.1]:   '$or': [ { preflight: false }, { preflight: [Object] } ] } 42 2020-05-05T11:26:45.203Z 2020-05-05T11:26:45.799Z 596\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u load@load.it:123456 -d '{\"text\":\"firstText\"}' https://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\n\nhttps://api.tiledesk.com/v2/5eb56e211f9e1f0012d6227b/requests\nù\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText20\"}' https://tiledesk-server-pre.herokuapp.com/5eb1116b4ec80100176671d2/requests\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 -d '{\"text\":\"firstText20\"}' https://tiledesk-server-pre.herokuapp.com/5ebae6f21aee9b0034511f65/requests\n\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj2\"}' http://localhost:3000/projects/5ea800091147f28c72b90c5e\n\n\n\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json'  http://localhost:3000/5ecf82d45c9a741d9c17bf42/labels\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrealeo@tiledesk.com:258456td -d '{\"event\":\"project.create\", \"target\":\"https://tiledesk-queues.herokuapp.com/webhook\"}' https://api.tiledesk.com/v2/5ec688ed13400f0012c2edc2/subscriptions\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrealeo@tiledesk.com:258456td -d '{\"event\":\"project.update\", \"target\":\"https://tiledesk-queues.herokuapp.com/webhook\"}' https://api.tiledesk.com/v2/5ec688ed13400f0012c2edc2/subscriptions\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrealeo@tiledesk.com:258456td -d '{\"event\":\"message.create\", \"target\":\"https://webhook.site/bbb5ec7b-1dd2-4b27-8cce-c0fad2b29fe6\"}' https://api.tiledesk.com/v2/5ec688ed13400f0012c2edc2/subscriptions\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrealeo@tiledesk.com:258456td -d '{\"event\":\"project.update\", \"target\":\"https://webhook.site/bbb5ec7b-1dd2-4b27-8cce-c0fad2b29fe6\"}' https://api.tiledesk.com/v2/5ec688ed13400f0012c2edc2/subscriptions\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"new_conversation\", \"attributes\": {\"attr1\":\"val1\"}}' http://localhost:3000/5ed77c22c9ad019b18984954/events\n\n\n\n\nJWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiJQVEFUU1Q2OUgxNEQ4NjJVIiwiZmlyc3RuYW1lIjoiUGVyc29uYWxlIiwibGFzdG5hbWUiOiJUZXN0IFBvcnRhbGUiLCJuYW1lIjoiVXRlbnRlIHRlc3QgcG9ydGFsZSBQVEEiLCJlbWFpbCI6InBlcnNvbmFsZS50ZXN0cG9ydGFsZUBzdHVkZW50aS51bmlzYWxlbnRvLml0IiwibWF0cmljb2xhZGlwZW5kZW50ZSI6IjE5OTk5OTkiLCJtYXRyaWNvbGFzdHVkZW50ZSI6bnVsbCwiY29kaWNlZmlzY2FsZSI6IlBUQVRTVDY5SDE0RDg2MlUiLCJzdWIiOiJ1c2VyZXh0ZXJuYWwiLCJhdWQiOiJodHRwczpcL1wvdGlsZWRlc2suY29tXC9wcm9qZWN0c1wvNWVjNjg4ZWQxMzQwMGYwMDEyYzJlZGMyIiwiaWF0IjoxNTkxMTgyMjcwLCJleHAiOjE1OTExODIzOTB9.IVoQYIS-TvsiQf4ZLIytqX2qsn3oyt7aC3usyf-4cCo\n\n\n\n\nJWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiJQVEFUU1Q2OUgxNEQ4NjJVIiwiZmlyc3RuYW1lIjoiUGVyc29uYWxlIiwibGFzdG5hbWUiOiJUZXN0IFBvcnRhbGUiLCJuYW1lIjoiVXRlbnRlIHRlc3QgcG9ydGFsZSBQVEEiLCJlbWFpbCI6InBlcnNvbmFsZS50ZXN0cG9ydGFsZUBzdHVkZW50aS51bmlzYWxlbnRvLml0IiwibWF0cmljb2xhZGlwZW5kZW50ZSI6IjE5OTk5OTkiLCJtYXRyaWNvbGFzdHVkZW50ZSI6bnVsbCwiY29kaWNlZmlzY2FsZSI6IlBUQVRTVDY5SDE0RDg2MlUiLCJzdWIiOiJ1c2VyZXh0ZXJuYWwiLCJhdWQiOiJodHRwczpcL1wvdGlsZWRlc2suY29tXC9wcm9qZWN0c1wvNWVjNjg4ZWQxMzQwMGYwMDEyYzJlZGMyIiwiaWF0IjoxNTkxMTgyMjcwLCJleHAiOjE1OTExODIzOTB9.IVoQYIS-TvsiQf4ZLIytqX2qsn3oyt7aC3usyf-4cCo\n\n\n\n\nJWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJfaWQiOiJQVEFUU1Q2OUgxNEQ4NjJVIiwiZmlyc3RuYW1lIjoiUGVyc29uYWxlIiwibGFzdG5hbWUiOiJUZXN0IFBvcnRhbGUiLCJuYW1lIjoiVXRlbnRlIHRlc3QgcG9ydGFsZSBQVEEiLCJlbWFpbCI6InBlcnNvbmFsZS50ZXN0cG9ydGFsZUBzdHVkZW50aS51bmlzYWxlbnRvLml0IiwibWF0cmljb2xhZGlwZW5kZW50ZSI6IjE5OTk5OTkiLCJtYXRyaWNvbGFzdHVkZW50ZSI6bnVsbCwiY29kaWNlZmlzY2FsZSI6IlBUQVRTVDY5SDE0RDg2MlUiLCJzdWIiOiJ1c2VyZXh0ZXJuYWwiLCJhdWQiOiJodHRwczpcL1wvdGlsZWRlc2suY29tXC9wcm9qZWN0c1wvNWVjNjg4ZWQxMzQwMGYwMDEyYzJlZGMyIiwiaWF0IjoxNTkxMTgyMjcwLCJleHAiOjE1OTExODIzOTB9.IVoQYIS-TvsiQf4ZLIytqX2qsn3oyt7aC3usyf-4cCo\n\n\n\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -H \"Authorization: JWT eyJhbGciOiJIUzI1NiJ9.eyJfaWQiOiIxMjMiLCJmaXJzdG5hbWUiOiJtYXJpbyIsImxhc3RuYW1lIjoicm9zc2kiLCJlbWFpbCI6Im1hcmlvcm9zc2lAZW1haWwuY29tIiwiY3VzdG9tQXR0ciI6ImMxIiwic3ViIjoidXNlcmV4dGVybmFsIiwiYXVkIjoiaHR0cHM6Ly90aWxlZGVzay5jb20vcHJvamVjdHMvNWVkOGI1Mjk0YWU1ZDBiZGUxNjNiYzA3In0.ePxJlIRre7i4s51DBWlLkfAYYjawXUCj9Eav-4r6s0A\" http://localhost:3000/5ed8b5294ae5d0bde163bc07/labels/\n\n\n\n\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@frontiere21.it:258456 https://tiledesk-server-pre.herokuapp.com/chat21/native/auth/createCustomToken\n\n\n\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin -d '{\"name\":\"testprj\"}' http://localhost:3000/projects\n\ncurl -v -X POST -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  -d '{ \"name\":\"segment1\", \"filters\": [{\"field\":\"field1\",\"operator\":\"=\",\"value\":\"ciao2\"}]}' http://localhost:3000/651446eeaf0e4e333f86db6d/segments\n\n\ncurl -v -X POST -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin -d '{\"text\":\"firstText\"}' http://localhost:3000/651446eeaf0e4e333f86db6d/requests/req123456999-651446eeaf0e4e333f86db6d/messages\n\n\ncurl -v -X GET -u admin@tiledesk.com:adminadmin  http://localhost:3000/651446eeaf0e4e333f86db6d/leads?segment=651448cc39405451f2165a80\n\n\nnumber\n\ncurl -v -X POST -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  -d '{ \"name\":\"segment1\", \"filters\": [{\"field\":\"field1\",\"operator\":\"=\",\"value\":44}]}' http://localhost:3000/651446eeaf0e4e333f86db6d/segments\n\ncurl -v -X GET -u admin@tiledesk.com:adminadmin  http://localhost:3000/651446eeaf0e4e333f86db6d/leads?segment=6515a7e0066727cb94bccd5c\n\n\n\n\nsudo systemctl start mongod\n\n\n\n\ncurl -v -X PUT -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  -d '{ \"name\":\"segment2\", \"filters\": [{\"field\":\"field1\",\"operator\":\"=\",\"value\":\"ciao2\"}]}' http://localhost:3000/651446eeaf0e4e333f86db6d/segments/6516eb0a11e143e3548b8dd6\n\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  http://localhost:3000/651446eeaf0e4e333f86db6d/segments/6516eb0a11e143e3548b8dd6\n\n\ncurl -v -X GET -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  http://localhost:3000/651446eeaf0e4e333f86db6d/segments/\n\n\ncurl -v -X DELETE -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin  http://localhost:3000/651446eeaf0e4e333f86db6d/segments/6516eb0a11e143e3548b8dd6\n"
  },
  {
    "path": "docs/api-mgm.md",
    "content": "# TILEDESK REST API\n\nPlease refer to the [Developer Portal](https://developer.tiledesk.com/)\n"
  },
  {
    "path": "docs/deploy.md",
    "content": "\n# Docker compose\n\n## Installation\n\n```\nsudo curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose\n\nsudo chmod +x /usr/local/bin/docker-compose\n\ndocker-compose up -d --force-recreate --build\n\n```\n\n# Docker Compose:\n\nsudo docker-compose up --force-recreate --build\n\n\nsudo docker-compose up  --env-file ./.env.list --force-recreate --build\n\n# Docker:\n\n## Build the Docker Image\n\n```\ndocker build -t tiledesk/tiledesk-server .\n\n```\n\n## Run Docker Container\n\n```\ndocker run -p 3000:3000 tiledesk/tiledesk-server\n```\n\n## Usefull commands\n```\ndocker images\ndocker ps\n```\n\n# Remove Container\n```\ndocker container ls -a\ndocker container rm id \n```\n\n\n```\ndocker run --name my-mongo -d mongo\nsudo docker run --env-file ./.env.list --link my-mongo:mongo tiledesk-server\n```\n\n\nExample .env.list file\n\nFIREBASE_APIKEY=123\nFIREBASE_CLIENT_EMAIL=firebase-adminsdk-a0r162123@chat21-pre23-01.iam.gserviceaccount.com\nFIREBASE_PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\\nMXYX\nFIREBASE_PROJECT_ID=tiledesk321-pre-01\nDATABASE_URI=mongodb://mongo/test\n\n\n# Tag an image referenced by Name and Tag\nTo tag a local image with name “httpd” and tag “test” into the “fedora” repository with “version1.0.test”:\n\ndocker tag tiledesk-server:test tiledesk/tiledesk-server:version1.0.5.test\n\n## Publish docker image\n\ndocker login with username (not email) and password \ndocker push tiledesk/tiledesk-server:latest\n\nhttps://buddy.works/guides/how-dockerize-node-application\n\n\n== NOT USED ==\n\ndocker run --name my-tiledesk-server --link my-mongo:mongo -d tiledesk-server -e FIREBASE_CONFIG_FILE=<FIREBASE_CONFIG_PATH.json> -e DATABASE_URI=mongodb://localhost/test\n\n\nsudo docker run --name my-tiledesk-server --link my-mongo:mongo -d tiledesk-server -e FIREBASE_CONFIG_FILE=/Users/andrealeo/dev/chat21/tiledesk-server/.firebasekey.json -e DATABASE_URI=mongodb://localhost/test\n\n\nsudo docker run --env FIREBASE_CONFIG_FILE=\"/Users/andrealeo/dev/chat21/tiledesk-server/.firebasekey.json\" tiledesk-server\n\n\n\nsudo docker run --env-file FIREBASE_CONFIG_FILE=\"/home/andrea/dev/tiledesk-server/.firebasekey.json\" tiledesk-server\n\n\nsudo docker run --env DATABASE_URI=\"mongodb://10.108.109.1/test\" --env-file ./.env.list --link my-mongo:mongo tiledesk-server\n\n\n# Disable heroku cache\n\nheroku config:set NODE_MODULES_CACHE=false -a tiledesk-server-pre\nheroku config:set NODE_MODULES_CACHE=true -a tiledesk-server-pre\n\n\n# Config set\nhttps://stackoverflow.com/questions/35483721/failed-to-replace-env-in-config-using-bash-npm\nnpm config set '//registry.npmjs.org/:_authToken' \"${NPM_TOKEN}\"\nnpm publish\n\n\nheroku config set \"//registry.npmjs.org/:_authToken\"=${NPM_TOKEN}  -a tiledesk-server-pre\n\n\n\n# Enable enterprise module:\n\nnpm run enable-ent\n\n\n# Change Local dependencies \nhttps://github.com/npm/npm/blob/b706d637d5965dbf8f7ce07dc5c4bc80887f30d8/doc/files/package.json.md#local-paths\n\n\n"
  },
  {
    "path": "docs/performance.md",
    "content": "loadtest http://localhost:7357/ -t 20 -c 10\n\n\n\n\n\n\n\n\n\n// db.getCollection('requests').find({\"id_project\":\"5ea800091147f28c72b90c5e\",\"status\":{\"$lt\":1000},\"$or\":[{\"preflight\":false},{\"preflight\":{\"$exists\":false}}]}).explain(\"executionStats\")\n// db.getCollection('requests').find({\"id_project\":\"5ea800091147f28c72b90c5e\",\"status\":{\"$lt\":1000},\"$or\":[{\"agents.id_user\":\"5e79e711ecb9230ac1f5b49f\"},{\"participants\":\"5e79e711ecb9230ac1f5b49f\"}]}).explain(\"executionStats\")\n\n\n\n\n// db.getCollection('requests').find({\"id_project\":\"5e7fc0d5045a6a5021a7e4be\",\"status\":{\"$lt\":1000},\"$or\":[{\"agents.id_user\":\"5e8a1eac904f2c304f9e8076\"},{\"participants\":\"5e8a1eac904f2c304f9e8076\"}]}).explain(\"executionStats\")\n\n\n\n2|www      | end ws 100 2020-05-03T07:43:17.240Z 2020-05-03T07:43:11.491Z\n end ws 100 2020-05-03T07:43:17.504Z 2020-05-03T07:43:12.385Z\n 3|www  | end ws 100 2020-05-03T07:45:11.904Z 2020-05-03T07:45:05.716Z\n\n\n                     2020-05-03T07:48:51.595Z 6secondi\n13|www  | end ws 100 2020-05-03T07:48:57.593Z \n \n\n\n| end ws 100 { id_project: '5e7fc0d5045a6a5021a7e4be',\n0|www  |   '$or':\n0|www  |    [ { 'agents.id_user': '5e9eab435170bc3889c948ce' },\n0|www  |      { participants: '5e9eab435170bc3889c948ce' } ] } 2020-05-03T07:56:23.415Z 2020-05-03T07:56:17.476Z 5 secondi\n\n1|www  | end ws 100 { id_project: '5e7fc0d5045a6a5021a7e4be',\n1|www  |   '$or':\n1|www  |    [ { 'agents.id_user': '5e8a1eac904f2c304f9e8076' },\n1|www  |      { participants: '5e8a1eac904f2c304f9e8076' } ] } 2020-05-03T07:56:23.673Z 2020-05-03T07:56:18.147Z 6secondi\n\n\n| end ws 100 { id_project: '5e7fc0d5045a6a5021a7e4be',\n14|www |   '$or':\n14|www |    [ { 'agents.id_user': '5e8a1eac904f2c304f9e8076' },\n14|www |      { participants: '5e8a1eac904f2c304f9e8076' } ] } 2020-05-03T08:15:10.192Z 2020-05-03T08:15:04.816Z 6secondi\n\n\n\n\nws 13 secondi se tolgo query or\n\n15|www | end ws 100 { id_project: '5e7fc0d5045a6a5021a7e4be' } 2020-05-03T08:23:37.938Z 2020-05-03T08:23:33.266Z 4secondi\n\n\n\n\nend ws 10 { id_project: '5e7fc0d5045a6a5021a7e4be',\n14|www |   '$or':\n14|www |    [ { 'agents.id_user': '5e8a1eac904f2c304f9e8076' },\n14|www |      { participants: '5e8a1eac904f2c304f9e8076' } ] } 2020-05-03T08:30:59.632Z 2020-05-03T08:30:59.115Z \n\n\nhttps://apixat.uv.es/5e7fc0d5045a6a5021a7e4be/requests?&page=0&limit=100\n\ncon tutto \n14|www | finish 2020-05-03T12:41:23.892Z 2020-05-03T12:41:25.456Z\nsu browser 16 secondi 500k gzip\n\nsenza populate senza sort\n7|www  | finish 2020-05-03T12:43:08.060Z 2020-05-03T12:43:08.396Z\nsu browser 1,6 secondi 41k gzip\n\nsenza populate con sort\n8|www  | finish 2020-05-03T12:44:28.098Z 2020-05-03T12:44:29.486Z\nsu browser 15 secondi 500k gzip\n\nuse tiledesk\nswitched to db tiledesk\n> db.requests.getIndexes()\n[\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"_id\" : 1\n\t\t},\n\t\t\"name\" : \"_id_\",\n\t\t\"ns\" : \"tiledesk.requests\"\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"request_id\" : 1\n\t\t},\n\t\t\"name\" : \"request_id_1\",\n\t\t\"background\" : true,\n\t\t\"ns\" : \"tiledesk.requests\"\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"requester\" : 1\n\t\t},\n\t\t\"name\" : \"requester_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"status\" : 1\n\t\t},\n\t\t\"name\" : \"status_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"participants\" : 1\n\t\t},\n\t\t\"name\" : \"participants_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"department\" : 1\n\t\t},\n\t\t\"name\" : \"department_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"tags\" : 1\n\t\t},\n\t\t\"name\" : \"tags_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"notes.text\" : 1\n\t\t},\n\t\t\"name\" : \"notes.text_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.id_project\" : 1\n\t\t},\n\t\t\"name\" : \"agents.id_project_1\",\n\t\t\"background\" : true,\n\t\t\"ns\" : \"tiledesk.requests\"\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.id_user\" : 1\n\t\t},\n\t\t\"name\" : \"agents.id_user_1\",\n\t\t\"background\" : true,\n\t\t\"ns\" : \"tiledesk.requests\"\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.uuid_user\" : 1\n\t\t},\n\t\t\"name\" : \"agents.uuid_user_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.role\" : 1\n\t\t},\n\t\t\"name\" : \"agents.role_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.user_available\" : 1\n\t\t},\n\t\t\"name\" : \"agents.user_available_1\",\n\t\t\"background\" : true,\n\t\t\"ns\" : \"tiledesk.requests\"\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"id_project\" : 1\n\t\t},\n\t\t\"name\" : \"id_project_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"createdAt\" : 1,\n\t\t\t\"type\" : -1\n\t\t},\n\t\t\"name\" : \"createdAt_1_type_-1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"id_project\" : 1,\n\t\t\t\"type\" : -1\n\t\t},\n\t\t\"name\" : \"id_project_1_type_-1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"_fts\" : \"text\",\n\t\t\t\"_ftsx\" : 1\n\t\t},\n\t\t\"name\" : \"request_fulltext\",\n\t\t\"default_language\" : \"italian\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"language_override\" : \"dummy\",\n\t\t\"background\" : true,\n\t\t\"weights\" : {\n\t\t\t\"rating_message\" : 1,\n\t\t\t\"transcript\" : 1\n\t\t},\n\t\t\"textIndexVersion\" : 2\n\t},\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"id_project\" : 1,\n\t\t\t\"status\" : 1,\n\t\t\t\"updatedAt\" : 1\n\t\t},\n\t\t\"name\" : \"id_project_1_status_1_updatedAt_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t},\n    db.requests.dropIndex( \"id_project_1_status_1_updatedAt_1\" )\n    db.requests.createIndex( { \"id_project\": 1, \"status\": 1, \"updatedAt\": -1 } )\n\n\t{\n\t\t\"v\" : 2,\n\t\t\"key\" : {\n\t\t\t\"agents.number_assigned_requests\" : 1\n\t\t},\n\t\t\"name\" : \"agents.number_assigned_requests_1\",\n\t\t\"ns\" : \"tiledesk.requests\",\n\t\t\"background\" : true\n\t}\n]\n> \n\n\n\n\n\n\n\nfai indice su toni createdAt e updatedAt  <----------->"
  },
  {
    "path": "docs/routes-answered.md",
    "content": "# Route `kb/answered` — domande con risposta (Knowledge Base)\n\nDocumentazione per [`routes/answered.js`](../routes/answered.js).\n\n## Scopo\n\nAPI REST per gestire le **domande già risposte** associate a un **namespace** della Knowledge Base del progetto. I documenti sono persistiti nel modello Mongoose `AnsweredQuestion` (vedi [`models/kb_setting.js`](../models/kb_setting.js)).\n\nOgni operazione è vincolata al **progetto corrente** (`req.projectid`, derivato dal segmento `:projectid` nell’URL) e alla validità del **namespace** (deve esistere un `Namespace` con `id` uguale al `namespace` richiesto e `id_project` uguale al progetto).\n\n## Montaggio e autenticazione\n\nIn [`app.js`](../app.js) il router è montato così:\n\n- **Path base:** `/:projectid/kb/answered`\n- **Middleware:** `passport.authenticate(['basic', 'jwt'])`, `validtoken`, `roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])`\n\nSono quindi richiesti autenticazione (Basic o JWT), token valido e ruolo **admin** oppure tipi **bot** / **subscription**.\n\nEsempio di URL completo (sviluppo locale):\n\n```http\nGET http://localhost:3000/{projectId}/kb/answered/{namespace}\n```\n\nSostituire `{projectId}` con l’ID MongoDB del progetto.\n\n## Modello dati (`AnsweredQuestion`)\n\n| Campo         | Tipo     | Note                                      |\n|---------------|----------|-------------------------------------------|\n| `id_project`  | string   | Impostato dal server dal contesto progetto |\n| `namespace`   | string   | ID del namespace (validato contro `Namespace`) |\n| `question`    | string   | Obbligatorio                              |\n| `answer`      | string   | Obbligatorio                              |\n| `tokens`      | number   | Opzionale                                 |\n| `request_id`  | string   | Opzionale                                 |\n| `created_at` / `updated_at` | date | Da `timestamps: true`              |\n\nIndici rilevanti: TTL su `created_at`, indice composto `(id_project, namespace, created_at)`, indice **testo** su `question` e `answer` (pesi question 5, answer 1) per la ricerca full-text.\n\n---\n\n## Endpoint\n\nTutti i path qui sotto sono relativi a `/:projectid/kb/answered`.\n\n### `POST /`\n\nAggiunge una nuova domanda risposta.\n\n**Body JSON**\n\n| Campo        | Obbligatorio | Descrizione        |\n|-------------|--------------|--------------------|\n| `namespace` | sì           | ID namespace       |\n| `question`  | sì           | Testo domanda      |\n| `answer`    | sì           | Testo risposta     |\n| `tokens`    | no           | Numero opzionale   |\n| `request_id`| no           | Riferimento richiesta |\n\n**Risposte**\n\n| HTTP | Significato |\n|------|-------------|\n| 200  | Documento salvato (corpo = documento MongoDB creato) |\n| 400  | Parametri mancanti (`namespace`, `question`, `answer`) |\n| 403  | Namespace non appartenente al progetto |\n| 500  | Errore server |\n\n---\n\n### `GET /:namespace`\n\nElenco paginato delle domande risposte per il namespace.\n\n**Query**\n\n| Parametro    | Default | Descrizione |\n|--------------|---------|-------------|\n| `page`       | `0`     | Pagina (0-based) |\n| `limit`      | `20`    | Elementi per pagina |\n| `sortField`  | `created_at` | Campo ordinamento |\n| `direction`  | `-1`    | `1` crescente, `-1` decrescente |\n| `search`     | —       | Se presente, attiva ricerca **full-text** (`$text`); l’ordinamento usa lo score di testo |\n\n**Risposta 200**\n\n```json\n{\n  \"count\": 0,\n  \"questions\": [],\n  \"query\": {\n    \"page\": 0,\n    \"limit\": 20,\n    \"sortField\": \"created_at\",\n    \"direction\": -1,\n    \"search\": \"...\"\n  }\n}\n```\n\n**Altri codici:** `400` (namespace mancante), `403` (namespace non valido per il progetto), `500`.\n\n---\n\n### `DELETE /:id`\n\nElimina una singola domanda per `_id` MongoDB.\n\n**Risposte:** `200` con `{ success, message }`, `404` se non trovata (o non del progetto), `500`.\n\n---\n\n### `DELETE /namespace/:namespace`\n\nElimina **tutte** le domande risposte per `namespace` nel progetto corrente.\n\n**Risposta 200:** `{ success, count: <deletedCount>, message }`.\n\n**403** se il namespace non appartiene al progetto. **500** in errore.\n\n---\n\n### `GET /count/:namespace`\n\nRestituisce il conteggio documenti per `id_project` + `namespace`.\n\n**Risposta 200:** `{ count: <number> }`.\n\n**400** / **403** / **500** come negli altri endpoint con validazione namespace.\n\n---\n\n## Funzione interna `validateNamespace(id_project, namespace_id)`\n\nVerifica che esista un documento `Namespace` con `id_project` e `id: namespace_id`. Usata in creazione, lettura elenco, cancellazione bulk e conteggio.\n\n---\n\n## Nota sull’ordine delle route Express\n\nNel file è definito `GET /:namespace` **prima** di `GET /count/:namespace`. In Express la prima route che corrisponde vince: una richiesta del tipo `GET .../kb/answered/count/<namespace>` può essere interpretata come `GET /:namespace` con `namespace` uguale alla stringa letterale `count`, invece che come endpoint di conteggio.\n\nPer far funzionare l’URL `GET /count/:namespace` come previsto dal codice, in genere occorre registrare **`GET /count/:namespace` prima di `GET /:namespace`**, oppure usare un prefisso path diverso per uno dei due. Verificare il comportamento in ambiente reale o adeguare l’ordine delle route se il conteggio non risponde come atteso.\n\n---\n\n## Riferimenti\n\n- Router: [`routes/answered.js`](../routes/answered.js)\n- Modello e indici: [`models/kb_setting.js`](../models/kb_setting.js) (`AnsweredQuestionSchema`)\n- Montaggio app: [`app.js`](../app.js) (`/:projectid/kb/answered`)\n"
  },
  {
    "path": "docs/testing.md",
    "content": "== \n\nnpm run test:int\n\n== Run specific test ==\n\nnpm test --  --grep 'Subscription./requests.create'\n\n\n\nmocha trigger.js   --grep 'Trigger.EventEmit'\n\nmocha trigger.js   --grep 'Trigger.EventEmitCreateRequest'\n\n\nmocha ./test/authenticationJwt.js    --grep 'AuthenticationJWT.signinJWt-Project-external-user-YESAudYesSubject'\n\nmocha ./test/requestRoute.js    --grep 'RequestRoute.getbyidWithPartecipatingBots'\n\nmocha ./test/requestRoute.js    --grep 'RequestRoute.create'\n\nmocha trigger.js   --grep 'Trigger.EventEmitCreateRequest'\n\n\nmocha messageService.js   --grep 'messageService.createMessageMultiLanguage'\n\n\n\nmocha authentication.js   --grep 'signInAnonymously.signInAnonymouslyReLogin'\n\n\n\n\n mocha subscriptionRequest.js   --grep 'Subscription.request.update-removeparticipant:'\n\n\n mocha requestService.js   --grep 'RequestService.addparticipant'"
  },
  {
    "path": "docs/upgrading.md",
    "content": "# Upgrading\n\nUse this procedure to upgrade from a previous version of Tiledesk server. You must manually execute the following upgrades depending on the starting version:\n\n# From the 2.0.7 version the schema updates are automatically managed by migrations\n\n# Upgrading from 2.0.X to 2.0.6\n\n## Add requests preflight field\n\n```\ndb.requests.update(\n  { preflight : { $exists: false }},\n  { $set: {\"preflight\": false} },\n  false,\n  true\n)\n```\n\n# Upgrading from 2.0.6 to 2.1.X\n\n## Add requests participantsAgents, participantsBots and hasBot fields\n\n```\ndb.requests.find( { participants: { $regex: /^bot_/ } } ).forEach(function(doc) {\n  doc.participantsBots = [doc.participants[0].replace(\"bot_\",\"\")];\n  doc.participantsAgents = [];\n  doc.hasBot = true;\n  db.requests.save(doc);\n});\n\ndb.requests.find( { \"participants\": { \"$not\": /^bot_/ } }).forEach(function(doc) {\n  doc.participantsAgents = doc.participants;\n  doc.participantsBots = [];\n  doc.hasBot = false;\n  db.requests.save(doc);\n});\n\n```\n"
  },
  {
    "path": "errorCodes.js",
    "content": "const errorCodes = {\n  AUTH: {\n    BASE_CODE: 10000,\n    ERRORS: {\n      USER_NOT_FOUND: 10001,\n      //AUTH_FAILED: 10002,\n      //PERMISSION_DENIED: 10003,\n      MISSING_VERIFICATION_CODE: 10004,\n      VERIFICATION_CODE_EXPIRED: 10005,\n      VERIFICATION_CODE_OTHER_USER: 10006\n    },\n  },\n  USER: {\n    BASE_CODE: 10000,\n    ERRORS: {\n      USER_NOT_FOUND: 10001,\n      AUTH_FAILED: 10002,\n      PERMISSION_DENIED: 10003,\n    },\n  },\n  PROJECT: {\n    BASE_CODE: 11000,\n    ERRORS: {\n      PROJECT_NOT_FOUND: 11001,\n      CREATE_FAILED: 11002,\n      ACCESS_DENIED: 11003,\n    },\n  },\n  CHATBOT: {\n    BASE_CODE: 12000,\n    ERRORS: {\n      DUPLICATE_SLUG: 12001\n    }\n  },\n  WEBHOOK: {\n    BASE_CODE: 13000,\n    ERRORS: {\n      NO_PRELOADED_DEV_REQUEST: 13001\n    }\n  }\n  // Aggiungi altre route\n};\n\nmodule.exports = errorCodes;"
  },
  {
    "path": "event/authEvent.js",
    "content": "const EventEmitter = require('events');\nvar RoleConstants = require(\"../models/roleConstants\");\n\nclass AuthEvent extends EventEmitter {\n    constructor() {\n        super();\n        this.queueEnabled = false;\n      }\n}\n\nconst authEvent = new AuthEvent();\n\n var projectuserUpdateKey = 'project_user.update';\nif (process.env.QUEUE_ENABLED === \"true\") {\n  projectuserUpdateKey = 'project_user.update.queue';\n}\n\nauthEvent.on(projectuserUpdateKey,  function(event) { \n      if (event.updatedProject_userPopulated) {\n        var pu = event.updatedProject_userPopulated;\n        if (pu.roleType === RoleConstants.TYPE_AGENTS) {          \n          authEvent.emit(\"project_user.update.agent\", event);\n        } else {\n          authEvent.emit(\"project_user.update.user\", event);\n        }\n      }\n});\n\n//listen for sigin and signup event\n\nmodule.exports = authEvent;\n"
  },
  {
    "path": "event/botEvent.js",
    "content": "const EventEmitter = require('events');\nconst messageEvent = require('../event/messageEvent');\nconst Faq_kb = require('../models/faq_kb');\nvar winston = require('../config/winston');\nconst cacheUtil = require(\"../utils/cacheUtil\");\nconst cacheEnabler = require(\"../services/cacheEnabler\");\nvar Faq = require(\"../models/faq\");\nconst { Webhook } = require('../models/webhook');\n\n// class BotEvent extends EventEmitter {}\nclass BotEvent extends EventEmitter {\n    \n    constructor() {\n        super();\n        this.queueEnabled = false;\n        this.setMaxListeners(11);\n    }\n\n\n    listen() {\n        //TODO modify to async\n        //messageEvent.on('message.received', function(message) {\n        var messageCreateKey = 'message.create';\n        if (messageEvent.queueEnabled) {\n            messageCreateKey = 'message.create.queue';\n        }\n\n        winston.info(\"Listening \" + messageCreateKey + \" event for Chatbot messages\");\n\n        messageEvent.on(messageCreateKey, function (message) {\n\n            winston.debug(\"message\", message);\n\n            // TODO usa meglio se attributes.reply_always=true\n\n            // if (message.sender === \"system\" && message.text && message.text!=\"\\\\start\") {\n            //     winston.debug(\"it s a message sent from system, exit\");\n            //     return null;\n            // }\n\n\n            //sbagliato\n            // if (message.sender === \"system\" && message.text && (message.text==\"\\\\start\" || message.text==\"/start\") ) {\n            //     winston.debug(\"it s a start message\");\n            // } else {\n            //     winston.debug(\"it s a message sent from system, exit\");\n            //     return null;\n            // }\n\n            if (message.sender === \"system\") {\n                if (message.text && (message.text == \"\\\\start\" || message.text == \"/start\")) {\n                    winston.debug(\"it s a start message\");\n                } else {\n                    winston.debug(\"it s a message sent from system, exit\");\n                    return null;\n                }\n            } else {\n                winston.debug(\"it s a message sent from other let s go\");\n            }\n\n            if (message.text && (message.text.indexOf(\"\\\\agent\") > -1 || message.text.indexOf(\"\\\\close\") > -1)) { //not reply to a message containing \\\\agent\n                return 0;\n            }\n\n            // if (message.text.startsWith(\"\\\\\")) { //not reply to a message containing \\\n            //     return null;\n            // }\n\n            var botId = getBotId(message);\n\n            winston.debug(\"botId: \" + botId);\n\n            if (!botId) {\n                return null;\n            } else {\n                //loop fix for messages sent from external bot    \n                // botprefix         \n                if (message.sender === 'bot_' + botId || message.sender === botId) {\n                    winston.debug(\"it s a message sent from bot, exit\");\n                    return null;\n                } else {\n                    messageEvent.emit('message.received.for.bot', message);  //UNUSED\n                }\n\n            }\n\n            // qui potresti leggere anche +secret ed evitare prossima query in botNotification\n            // let qbot = Faq_kb.findById(botId);  //TODO add cache_bot_here\n            let qbot = Faq_kb.findById(botId).select('+secret')\n            //TODO unselect secret. secret is unselectable by default in the model\n\n            if (cacheEnabler.faq_kb) {\n                winston.debug('message.id_project+\":faq_kbs:id:\"+botId: ' + message.id_project + \":faq_kbs:id:\" + botId);\n                // qbot.cache(cacheUtil.defaultTTL, message.id_project+\":faq_kbs:id:\"+botId)\n                qbot.cache(cacheUtil.defaultTTL, message.id_project + \":faq_kbs:id:\" + botId + \":secret\")\n                winston.debug('faq_kb cache enabled');\n            }\n\n            qbot.exec(function (err, bot) {\n\n                if (err) {\n                    winston.error('Error getting object.', err);\n                    return 0;\n                }\n                if (!bot) {\n                    winston.warn('Bot not found with id ' + botId);\n                }\n\n                winston.debug(\"bot debug\", bot);\n                winston.debug('bot debug secret: ' + bot.secret);\n\n                if (bot) {\n                    if (bot.type === \"internal\") {\n                        botEvent.emit('bot.message.received.notify.internal', message);\n\n                    } else {  //external \n                        if (bot.url) {\n                            var botNotification = { bot: bot, message: message };\n                            botEvent.emit('bot.message.received.notify.external', botNotification);\n                        } else {\n                            winston.warn(\"bot url is not defined\", bot);\n                        }\n                    }\n                }\n\n            });\n\n        });\n\n        botEvent.on('faqbot.update.virtual.delete', async (chatbot) => {\n            winston.verbose(\"botEvent ON faqbot.update.virtual.delete: \", chatbot);\n\n            if (chatbot.publishedAt) {\n                // Stop the flow if the chatbot is a published one\n                return;\n            }\n\n            await Faq.updateMany({ id_faq_kb: chatbot._id }, { trashed: true, trashedAt: chatbot.trashedAt }).catch((err) => {\n                winston.error(\"Event faqbot.update.virtual.delete error updating faqs \", err);\n            })\n\n            let deletedW = await Webhook.findOneAndDelete({ chatbot_id: chatbot._id }).catch((err) => {\n                winston.error(\"Error deleting webhook on chatbot deleting: \", err);\n            })\n\n            let publishedChatbots = await Faq_kb.find({ root_id: chatbot._id }, { _id: 1 }).catch((err) => {\n                winston.error(\"Event faqbot.update.virtual.delete error getting all published chatbots \", err);\n            })\n\n            const publishedChatbotIds = publishedChatbots.map(c => c._id);\n\n            if (publishedChatbotIds.length > 0) {\n\n                const batchSize = 20;\n                const sleep_ms = 500;\n                const batches = [];\n                for (let i = 0; i < publishedChatbotIds.length; i += batchSize) {\n                    batches.push(publishedChatbotIds.slice(i, i + batchSize));\n                }\n\n                for (const batch of batches) {\n                    await Faq_kb.updateMany(\n                        { _id: { $in: batch } },\n                        { $set: { trashed: true, trashedAt: chatbot.trashedAt } }\n                    );\n\n                    await Faq.updateMany(\n                        { id_faq_kb: { $in: batch } },\n                        { $set: { trashed: true, trashedAt: chatbot.trashedAt } }\n                    );\n                \n                    await sleep(sleep_ms);\n                }                \n\n            }\n        })\n\n        function sleep(ms) {\n            return new Promise(resolve => setTimeout(resolve, ms));\n          }\n\n    }\n\n}\n\nconst botEvent = new BotEvent();\n\n//TODO use request. getBotId\nfunction getBotFromParticipants(participants) {\n    var botIdTmp;\n  \n    if (participants) {\n      participants.forEach(function(participant) { \n        winston.debug(\"participant\", participant);\n        \n        // botprefix\n        if (participant.indexOf(\"bot_\")> -1) {\n            // botprefix\n          botIdTmp = participant.replace(\"bot_\",\"\");\n          winston.debug(\"botIdTmp\", botIdTmp);\n          //break;        \n        }\n      });\n    \n      return botIdTmp;\n    }else {\n      return null;\n    }\n  }\n\n//TODO use request. getBotId\nfunction getBotId(message) {\n    var sender = message.sender;\n    winston.debug(\"sender\", sender);\n \n    if (sender==\"sytem\") {\n         return null;\n    }\n \n    var recipient = message.recipient;\n    winston.debug(\"recipient\", recipient);\n \n    // botprefix\n    if (recipient.startsWith('bot_')) {\n        // botprefix\n        return recipient.replace('bot_','');\n    }\n    // var text = message.text;\n    // winston.debug(\"text\", text);\n    \n    if ( message.request== null || message.request.participants == null) {\n        return null;\n    }\n\n    var participants = message.request.participants;\n    winston.debug(\"participants\", participants);\n \n    var botId = getBotFromParticipants(participants);\n    winston.debug(\"botId: \" + botId);\n \n   if (botId) {\n      return botId;\n   }else {\n       return null;\n   }\n \n}\n\n\n\nmodule.exports = botEvent;\n"
  },
  {
    "path": "event/connectionEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass ConnectionEvent extends EventEmitter {}\n\nconst connectionEvent = new ConnectionEvent();\n\nmodule.exports = connectionEvent;\n"
  },
  {
    "path": "event/departmentEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass DepartmentEvent extends EventEmitter {}\nvar winston = require('../config/winston');\nvar Request = require('../models/request');\n\nconst departmentEvent = new DepartmentEvent();\n\n\n// rules engine ascolta evento operators.select ed in base a regole custom riassegna oggetto operators\n\nDepartmentEvent.prototype.callNextEvent = function(nextEventName, res) {\n  var operatorSelectedEvent = res.result;\n  var count = departmentEvent.listenerCount(nextEventName);\n  winston.debug('count', count);   \n  if (count<1) {\n    winston.debug('operator.select count <1 return default resolve');   \n    return res.resolve(operatorSelectedEvent);\n  } else {\n    winston.debug('operator.select count >1 launch ' + nextEventName);\n    departmentEvent.emit(nextEventName, res);   \n  }\n}\n\ndepartmentEvent.on('operator.select.base1', function(res) {\n\n  return departmentEvent.callNextEvent('operator.select.base2', res);\n\n    \n  });\n\n\n\nmodule.exports = departmentEvent;\n"
  },
  {
    "path": "event/emailEvent.js",
    "content": "const EventEmitter = require('events');\nconst project_user = require('../models/project_user');\nvar winston = require('../config/winston');\nconst user = require('../models/user');\nconst roleConstants = require('../models/roleConstants');\n\nclass EmailEvent extends EventEmitter {\n    constructor() {\n        super();\n        this.queueEnabled = false;\n    }\n\n    listen() {\n\n        emailEvent.on('email.send.quote.checkpoint', function(data) {\n\n            // TODO setImmediate here?\n            winston.debug(\"emailEvent data: \", data);\n        \n            project_user.findOne({ id_project: data.id_project, role: roleConstants.OWNER }, (err, puser) => {\n        \n                if (err) {\n                    winston.error(\"error finding owner user: \" + err);\n                    return;\n                }\n        \n                if (!puser) {\n                    winston.error(\"Owner user not found. Unable to send checkpoint quota reached.\");\n                    return;\n                }\n        \n                user.findOne({ _id: puser.id_user}, (err, user) => {\n        \n                    if (err) {\n                        winston.error(\"Error finding user: \", err);\n                        return\n                    }\n        \n                    if (!user) {\n                        winston.error(\"User not found. Unable to send checkpoint quota reached.\")\n                        return;\n                    }\n        \n                    let resource_name;\n                    if (data.type == 'requests') {\n                        resource_name = 'Conversations'\n                    }\n                    if (data.type == 'tokens') {\n                        resource_name = 'AI Tokens'\n                    }\n                    if (data.type == 'email') {\n                        resource_name = 'Chatbot Email'\n                    }\n                    \n                    const emailService = require('../services/emailService'); // imported here to ensure that the emailService instance was already created\n\n                    emailService.sendEmailQuotaCheckpointReached(user.email, user.firstname, data.project_name, resource_name, data.checkpoint, data.quotes);\n                })\n        \n            \n            })\n        \n          });\n    }\n}\n\nconst emailEvent = new EmailEvent();\nemailEvent.listen();\n\nmodule.exports =  emailEvent;\n"
  },
  {
    "path": "event/faqBotEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass FaqBotEvent extends EventEmitter {}\n\nconst faqBotEvent = new FaqBotEvent();\n\n\n\n\nmodule.exports = faqBotEvent;\n"
  },
  {
    "path": "event/groupEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass GroupEvent extends EventEmitter {}\n\nconst groupEvent = new GroupEvent();\n\n\n\nmodule.exports = groupEvent;\n"
  },
  {
    "path": "event/integrationEvent.js",
    "content": "const EventEmitter = require('events');\n\nlet winston = require('../config/winston');\n\nclass IntegrationEvent extends EventEmitter {\n    constructor() {\n        super();\n    }\n}\n\nconst integrationEvent = new IntegrationEvent();\n\nmodule.exports = integrationEvent;"
  },
  {
    "path": "event/labelEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass LabelEvent extends EventEmitter {}\n\nconst labelEvent = new LabelEvent();\n\n\n\n\nmodule.exports = labelEvent;\n"
  },
  {
    "path": "event/leadEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass LeadEvent extends EventEmitter {\n    constructor() {\n        super();\n        this.queueEnabled = false;\n        this.setMaxListeners(11);\n      }\n}\n\nconst leadEvent = new LeadEvent();\n\n\nmodule.exports = leadEvent;\n"
  },
  {
    "path": "event/message2Event.js",
    "content": "var winston = require('../config/winston');\nvar MessageConstants = require(\"../models/messageConstants\");\n// var messageEvent = require(\"../event/messageEvent\");\n\n\n\nvar EventEmitter2 = require('eventemitter2').EventEmitter2;\n\n\nclass Message2Event extends EventEmitter2 {}\n\nconst message2Event = new Message2Event({\n\n  //\n  // set this to `true` to use wildcards. It defaults to `false`.\n  //\n  wildcard: true,\n});\n\nwinston.debug(\"message2Event init\");\n\n\n\n// messageEvent.on('message.create', function(message) {\n\n//   winston.debug(\"message2Event message.create\", message);\n\n//   message2Event.emit('message.create', message);\n\n//   if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED) {\n//     winston.debug(\"message2Event.emit message.received\", message);\n//     message2Event.emit('message.received', message);\n//   }\n\n//   if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING) {\n//     winston.debug(\"message2Event.emit message.sending\", message);\n//     message2Event.emit('message.sending', message);\n//   }\n// });\n\n// messageEvent.on('message.create.first', function(message) {\n//   winston.debug(\"message2Event.emit message.create.first\", message);\n//   message2Event.emit('message.create.first', message);\n// });\n\n// messageEvent.on('message.create.from.requester', function(message) {\n//   winston.debug(\"message2Event.emit message.create.from.requester\", message);\n//   message2Event.emit('message.create.from.requester', message);\n// });\n\n\n\n\n\n// messageEvent.on('message.update', function(message) {\n\n//   winston.debug(\"message2Event message.update\", message);\n\n//   message2Event.emit('message.update', message);\n\n// });\n\n\nmodule.exports = message2Event;\n"
  },
  {
    "path": "event/messageEvent.js",
    "content": "const EventEmitter = require('events');\nvar winston = require('../config/winston');\nvar Request = require(\"../models/request\");\nvar Message = require(\"../models/message\");\nvar Faq_kb = require(\"../models/faq_kb\");\nvar MessageConstants = require(\"../models/messageConstants\");\nvar message2Event = require(\"../event/message2Event\");\nvar requestEvent = require(\"../event/requestEvent\");\n\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\n\nclass MessageEvent extends EventEmitter { \n  constructor() {\n    super();\n    this.queueEnabled = false;\n  }\n}\n\nconst messageEvent = new MessageEvent();\n\n\n\nfunction emitCompleteMessage(message) {\n  if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED) {\n    winston.debug(\"messageEvent.emit message.received\", message);\n    messageEvent.emit('message.received', message);\n  }\n\n  if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING) {\n    winston.debug(\"messageEvent.emit message.sending\", message);\n    messageEvent.emit('message.sending', message);\n  }\n}\n\nmessageEvent.on('message.create', emitCompleteMessage);\n\n// messageEvent.on('message.update.simple', emitCompleteMessage);     //if populateMessageUpdate is disabled then you must forward message.update event from message.update.simple\nmessageEvent.on('message.update', emitCompleteMessage);         // i must restore populateMessageWithRequest. see below\n\nfunction populateMessageCreate(message) {\n  return populateMessageWithRequest(message, 'message.create');\n}\nfunction populateMessageUpdate(message) {\n  return populateMessageWithRequest(message, 'message.update');\n  // return; // do not populate message.update it's not used by anyone. \n        ///it is used by \\agent interceptor. Without populateMessageWithRequest \\agent sent by bot doesn't work. i must restore populateMessageWithRequest\n          //  Not used by webhook. populate for message.update is slow.\n}\n\n\nfunction populateMessageWithRequest(message, eventPrefix) {\n\n  \n  winston.debug(\"populateMessageWithRequest \"+eventPrefix, message.toObject());\n  winston.debug(\"populateMessageWithRequest \"+eventPrefix +\" \"+ message.text);\n  \n  var messageJson = message.toJSON();\n\n  \n    // cacherequest      // requestcachefarequi populaterequired cacheveryhightpriority\n    \n  let q = Request.findOne({request_id:  message.recipient, id_project: message.id_project}).\n  populate('lead').\n  populate('department').  \n  populate('participatingBots').\n  populate('participatingAgents').       \n  populate({path:'requester',populate:{path:'id_user'}}).\n  lean();\n\n  \n  //perche lean?\n  // TODO availableAgentsCount nn c'è per il lean problema trigger\n  // request.department._id DA CORREGGERE ANCHE PER REQUEST.CREATE\n  // request.department.hasBot \n  // request.isOpen\n  winston.debug('message Event populate');\n  if (cacheEnabler.request) {\n    q.cache(cacheUtil.defaultTTL, message.id_project+\":requests:request_id:\"+message.recipient) //request_cache ma con lean????attento metti a parte\n    winston.debug('request cache enabled');\n  }\n  q.exec(function (err, request) {\n\n    if (err) {\n      winston.error(\"Error getting request on messageEvent.populateMessage\",err );\n      return messageEvent.emit(eventPrefix, message);\n    }\n\n    winston.debug('message Event populate after query');\n\n\n  if (request) {\n      winston.debug(\"request is defined in messageEvent\",request );\n      \n      // var requestJson = request.toJSON();\n      var requestJson = request;\n    \n      if (request.department && request.department.id_bot) {\n        // if (request.department) {\n        let qbot = Faq_kb.findById(request.department.id_bot)\n\n        if (cacheEnabler.faq_kb) {\n          qbot.cache(cacheUtil.defaultTTL, message.id_project+\":faq_kbs:id:\"+request.department.id_bot)\n          winston.debug('faq_kb cache enabled');\n        }\n\n        qbot.exec(function(err, bot) {\n          winston.debug('bot', bot);\n          requestJson.department.bot = bot\n          \n          messageJson.request = requestJson;\n          // winston.debug('messageJson', messageJson);\n          winston.debug(\"message.emit\",messageJson );\n          messageEvent.emit(eventPrefix,messageJson );\n\n          //se a req.first_text toglio ritorni a capo è sempre diverso da msg.txt\n          if (message.text === request.first_text){\n            messageEvent.emit(eventPrefix+'.first', messageJson );\n          }\n\n          //  request.lead can be undefined because some test case uses the old deprecated method requestService.createWithId.\n          // TODO lead_id used. Change it?\n          if (request.lead && message.sender === request.lead.lead_id) {\n            messageEvent.emit(eventPrefix+'.from.requester', messageJson );\n          }\n\n//           olumn\":21,\"file\":\"/app/node_modules/mongoose/lib/model.js\",\"function\":null,\"line\":4869,\"method\":null,\"native\":false},{\"column\":11,\"file\":\"/app/node_modules/mongoose/lib/query.js\",\"function\":\"_hooks.execPost\",\"line\":4391,\"method\":\"execPost\",\"native\":false},{\"column\":16,\"file\":\"/app/node_modules/kareem/index.js\",\"function\":null,\"line\":135,\"method\":null,\"native\":false},{\"column\":9,\"file\":\"internal/process/task_queues.js\",\"function\":\"processTicksAndRejections\",\"line\":79,\"method\":null,\"native\":false}]}\n// 2021-01-26T10:30:16.045281+00:00 app[web.1]: error: uncaughtException: Cannot read property 'name' of undefined\n// 2021-01-26T10:30:16.045283+00:00 app[web.1]: TypeError: Cannot read property 'name' of undefined\n// 2021-01-26T10:30:16.045284+00:00 app[web.1]:     at /app/event/messageEvent.js:101:80\n// 2021-01-26T10:30:16.045284+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:4846:16\n// 2021-01-26T10:30:16.045285+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/helpers/promiseOrCallback.js:24:16\n// 2021-01-26T10:30:16.045285+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:4869:21\n// 2021-01-26T10:30:16.045286+00:00 app[web.1]:     at _hooks.execPost (/app/node_modules/mongoose/lib/query.js:4391:11)\n// 2021-01-26T10:30:16.045286+00:00 app[web.1]:     at /app/node_modules/kareem/index.js:135:16\n// 2021-01-26T10:30:16.045287+00:00 app[web.1]: \n\n          message2Event.emit(eventPrefix+'.request.channel.' + request.channel.name, messageJson );\n          message2Event.emit(eventPrefix+'.request.channelOutbound.' + request.channelOutbound.name, messageJson );\n          message2Event.emit(eventPrefix+'.channel.' + message.channel.name, messageJson );\n\n        });\n\n        \n      }else {\n        messageJson.request = requestJson;\n        winston.debug(\"message.emit\",messageJson );\n        messageEvent.emit(eventPrefix, messageJson );   \n        \n        if (message.text === request.first_text) {\n          messageEvent.emit(eventPrefix+'.first', messageJson );\n        }\n\n        //  request.lead can be undefined because some test case uses the old deprecated method requestService.createWithId.\n        // TODO lead_id used. Change it?\n        if (request.lead && message.sender === request.lead.lead_id) {\n        // if (message.sender === request.lead.lead_id) {\n          winston.debug(\"message.create.from.requester\",messageJson );\n          messageEvent.emit(eventPrefix+'.from.requester', messageJson );\n        }\n\n        message2Event.emit(eventPrefix+'.request.channel.' + request.channel.name, messageJson );\n        message2Event.emit(eventPrefix+'.request.channelOutbound.' + request.channelOutbound.name, messageJson );\n        message2Event.emit(eventPrefix+'.channel.' + message.channel.name, messageJson );\n\n      }   \n          \n  } else {\n    winston.debug(\"request is undefined in messageEvent. Is it a direct or group message ?\" );\n    messageEvent.emit(eventPrefix,messageJson );\n    message2Event.emit(eventPrefix+'.channel.' + message.channel.name, messageJson );\n  }\n   \n  });\n\n}\n\nmessageEvent.on('message.create.simple', populateMessageCreate);\nmessageEvent.on('message.update.simple', populateMessageUpdate);\n\n// When the user (lead/requester) sends a message, reopen the conversation if it was pending\nmessageEvent.on('message.create.from.requester', function (messageJson) {\n  if (!messageJson.request || messageJson.request.workingStatus !== 'pending') return;\n  var request_id = messageJson.request.request_id;\n  var id_project = messageJson.request.id_project;\n  Request.findOneAndUpdate(\n    { request_id: request_id, id_project: id_project },\n    { $set: { workingStatus: 'open' } },\n    { new: true },\n    function (err, updatedRequest) {\n      if (err) {\n        winston.error(\"Error updating request workingStatus from pending to open\", err);\n        return;\n      }\n      if (updatedRequest) {\n        winston.debug(\"Request workingStatus set to open (was pending)\", { request_id, id_project });\n        requestEvent.emit('request.workingStatus.update', { request: updatedRequest });\n        requestEvent.emit('request.update', updatedRequest);\n      }\n    }\n  );\n});\n\n\n\n// // riattiva commentato per performance\n\n// // spostare su classe\n\n\n\n// var messageCreateKey = 'message.create';\n// if (messageEvent.queueEnabled) {\n//   messageCreateKey = 'message.create.queue';\n// }\n// winston.debug(\"messageEvent.queueEnabled: \"+messageEvent.queueEnabled); \n\n// winston.debug(\"messageCreateKey: \"+messageCreateKey); \n\n// messageEvent.on(messageCreateKey, function(message) {\n//   setImmediate(() => {      \n//     winston.debug(\"message.create before\");\n//     if (!message.request) {\n//       return;\n//     }\n//     let request_id = message.request.request_id;\n//     let id_project = message.request.id_project;\n\n\n//     //update waiitng time if write an  agent (member of participants)\n//     let visitor_sent_last_message = false;\n//     // winston.info(\" message.request.snapshot.lead.lead_id: \"+  message.request.snapshot.lead.lead_id);\n//     // winston.info(\" message.sender: \"+  message.sender);\n\n//     if (message.request.snapshot && message.request.snapshot.lead.lead_id == message.sender) { \n//       visitor_sent_last_message = true;\n//     }\n\n\n\n//     // don't work for recursive call\n//     // requestService.incrementMessagesCountByRequestId(message.request._id, message.request.id_project).then(function (savedRequest) {\n//     //   winston.info(\"incremented request\", savedRequest);\n//     // });\n//     let clonedmessage = Object.assign({}, message);\n//     delete clonedmessage.request\n    \n    \n//     let data = {\n//       $push: {\n//         \"snapshot.messages.data\": {\n//               $each: [ clonedmessage ],\n//               $slice: -30\n//             }\n//       },\n//       $inc : {'snapshot.messages.messages_count' : 1},\n//       \"snapshot.messages.visitor_sent_last_message\": visitor_sent_last_message,\n//       \"snapshot.messages.last_message_timestamp\": message.createdAt\n//     };\n\n//     // db.getCollection('requests').find({\"$expr\": { \"$gt\": [ \"$snapshot.messages.visitor_last_message_timestamp\", \"$snapshot.messages.agent_last_message_timestamp\"]}})\n    \n    \n//     if (visitor_sent_last_message) {\n//       data[\"snapshot.messages.visitor_last_message_timestamp\"]= message.createdAt;\n//     } else {\n//       data[\"snapshot.messages.agent_last_message_timestamp\"]= message.createdAt;\n//     }\n//     // db.getCollection('requests').updateOne({\"request_id\":\"support-group-630600bfaf7cd942116bc993-3da378ec63924bb9b4934b2835b37a7c\"},{\"$push\":{\"snapshot.messages.data\":{\"$each\":[\"s\"],\"$slice\":-5}}}}})\n//     winston.debug(\"data\", data);\n\n//     return Request       \n//             .findOneAndUpdate({request_id: request_id, id_project: id_project}, data, {new: true, upsert:false}, function(err, updatedRequest) {\n//                 if (err) {\n//                   winston.error(err);\n//                   return reject(err);\n//                 }\n//                 winston.info(\"Message count +1\");\n                \n//               });\n\n//   });\n// });\n\n\nmodule.exports = messageEvent;"
  },
  {
    "path": "event/messagePromiseEvent.js",
    "content": "const EventEmitter = require('promise-events');\n\nclass MessagePromiseEvent extends EventEmitter {}\n\nconst messagePromiseEvent = new MessagePromiseEvent();\n\n\n\n\nmodule.exports = messagePromiseEvent;\n"
  },
  {
    "path": "event/projectEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass ProjectEvent extends EventEmitter {}\n\nconst projectEvent = new ProjectEvent();\n\n\n\n\nmodule.exports = projectEvent;\n"
  },
  {
    "path": "event/projectUserEvent.js",
    "content": "const EventEmitter = require('events');\nconst winston = require('../config/winston');\nconst Group = require(\"../models/group\");\n\nclass ProjectUserEvent extends EventEmitter {\n    constructor() {\n        super();\n        this.registerListeners();\n    }\n\n    registerListeners() {\n\n        this.on('project_user.deleted', async (pu) => {\n\n            try {\n                winston.debug('[project_user.deleted] Event catched:', pu);\n    \n                const id_project = pu.id_project;\n                let id_user = pu.id_user.toString();\n                if (typeof id_user === 'object' && id_user._id) {\n                    id_user = id_user._id.toString();\n                }\n    \n                const result = await Group.updateMany({ id_project: id_project }, { $pull: { members: id_user }});\n                winston.verbose(`Event project_user.deleted: User ${id_user} removed from ${result?.nModified} groups.`)\n\n            } catch (err) {\n                winston.verbose(`Event project_user.deleted: Error removing user ${id_user} from groups: `, err);\n            }\n\n        });\n\n    }\n}\n\n// Istanza singleton\nconst puEvent = new ProjectUserEvent();\n\nmodule.exports = puEvent;\n"
  },
  {
    "path": "event/requestEvent.js",
    "content": "const EventEmitter = require('events');\nvar Message = require(\"../models/message\");\nvar Request = require(\"../models/request\");\n\nvar winston = require('../config/winston');\n\nclass RequestEvent extends EventEmitter {\n    constructor() {\n        super();\n        this.queueEnabled = false;\n        this.setMaxListeners(11);\n      }\n}\n\nconst requestEvent = new RequestEvent();\n\n\nrequestEvent.on('request.create.simple', function(request, snapshot) {\n\n\n    // TODO setImmediate here?\n    winston.debug('requestEvent here', request);\n    winston.debug('executin query populate on requestEvent');\n\n    winston.debug(\"request.create.simple\");\n    //no cache required here. because is always new (empty)\n    request\n        .populate(\n            [           \n            {path:'department'},\n            {path:'lead'},\n            {path:'participatingBots'},\n            {path:'participatingAgents'},                                         \n            {path:'requester',populate:{path:'id_user'}}\n            ]\n        )\n        .execPopulate( function(err, requestComplete) {\n\n            if (err){\n                winston.error('error getting request', err);\n                return requestEvent.emit('request.create', request);\n            }\n\n            winston.debug('emitting request.create', requestComplete.toObject());\n\n            requestEvent.emit(\"request.snapshot.update\", { request: request, snapshot: snapshot });\n            requestEvent.emit('request.create', requestComplete);\n\n            //with request.create no messages are sent. So don't load messages\n        // Message.find({recipient:  request.request_id, id_project: request.id_project}).sort({updatedAt: 'asc'}).exec(function(err, messages) {                  \n        //   if (err) {\n        //         winston.error('err', err);\n        //   }\n        //   winston.debug('requestComplete',requestComplete.toObject());\n        //   requestComplete.messages = messages;\n        //   requestEvent.emit('request.create', requestComplete);\n\n        // //   var requestJson = request.toJSON();\n        // //   requestJson.messages = messages;\n        // //   requestEvent.emit('request.create', requestJson);\n        // });\n    });\n  });\n\n\nmodule.exports = requestEvent;\n"
  },
  {
    "path": "event/roleEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass RoleEvent extends EventEmitter {}\n\nconst roleEvent = new RoleEvent();\n\n\n\nmodule.exports = roleEvent;\n"
  },
  {
    "path": "event/subscriptionEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass SubscriptionEvent extends EventEmitter {}\nvar winston = require('../config/winston');\n\n\nconst subscriptionEvent = new SubscriptionEvent();\n\n\n\nmodule.exports = subscriptionEvent;\n"
  },
  {
    "path": "jobs.js",
    "content": "\nvar dotenvPath = undefined;\n\nif (process.env.DOTENV_PATH) {\n  dotenvPath = process.env.DOTENV_PATH;\n  console.log(\"load dotenv form DOTENV_PATH\", dotenvPath);\n}\n\nif (process.env.LOAD_DOTENV_SUBFOLDER ) {\n  console.log(\"load dotenv form LOAD_DOTENV_SUBFOLDER\");\n  dotenvPath = __dirname+'/confenv/.env';\n}\n\nrequire('dotenv').config({ path: dotenvPath});\n\n\nvar mongoose = require('mongoose');\n\nlet winston = require('./config/winston');\nlet JobsManager = require('./jobsManager');\n\n\nlet geoService = require('./services/geoService');\n// let subscriptionNotifier = require('./services/subscriptionNotifier');\nvar subscriptionNotifierQueued = require('./services/subscriptionNotifierQueued');\nvar botSubscriptionNotifier = require('./services/BotSubscriptionNotifier');\n\nconst botEvent = require('./event/botEvent');\nvar channelManager = require('./channels/channelManager');\n\nvar updateLeadQueued = require('./services/updateLeadQueued');\nvar updateRequestSnapshotQueued = require('./services/updateRequestSnapshotQueued');\n\n\nrequire('./services/mongoose-cache-fn')(mongoose);\n\n\nvar config = require('./config/database');\n\n\n//override JOB_WORKER_ENABLED to false when you start jobs.js\nprocess.env.JOB_WORKER_ENABLED=false\n\nvar databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\nvar autoIndex = true;\n\nif (!databaseUri) { //TODO??\n  winston.warn('DATABASE_URI not specified, falling back to localhost.');\n}\n\nlet useUnifiedTopology = process.env.MONGOOSE_UNIFIED_TOPOLOGY === 'true';\nwinston.info(\"DB useUnifiedTopology: \", useUnifiedTopology, typeof useUnifiedTopology);\n\nvar connection = mongoose.connect(databaseUri, { \"useNewUrlParser\": true, \"autoIndex\": autoIndex, \"useUnifiedTopology\": useUnifiedTopology }, function(err) {\n  if (err) { \n    winston.error('Failed to connect to MongoDB on ' + databaseUri + \" \", err);\n    process.exit(1);\n  }\nwinston.info(\"Mongoose connection done on host: \"+mongoose.connection.host + \" on port: \" + mongoose.connection.port + \" with name: \"+ mongoose.connection.name)// , mongoose.connection.db);\n});\n// winston.info(\"mongoose.connection\",mongoose.connection);\n// module.exports = jobsManager;\n\n\n\nasync function main()\n{\n\n  ////************* LOAD QUEUE ************ //\n  require('./pubmodules/cache').cachegoose(mongoose);            \n      \n\n  ////************* LOAD CONCIERGE BOT ************ //\n  require('./pubmodules/rules/appRules').start();\n\n\n  // require('./pubmodules/trigger/rulesTrigger').listen(); request.close trigger event is not triggered by anyone now?\n   \n\n  //************* LOAD QUEUE ************ //\n  require('./pubmodules/queue');   \n    // require('@tiledesk-ent/tiledesk-server-queue');     \n\n   //************* LOAD CHAT21 ************ //\n   channelManager.listen(); // chat21Handler is loaded with stadard events like request.create and NOT request.create.queue because it is used internally by the worker when the request is closed by ChatUnhandledRequestScheduler\n\n    \n\n\n    let jobsManager = new JobsManager(undefined, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued, updateRequestSnapshotQueued);\n\n    jobsManager.listen();\n\n\n    let emailNotification = require('./pubmodules/emailNotification');\n    jobsManager.listenEmailNotification(emailNotification);\n\n   \n    let activityArchiver = require('./pubmodules/activities').activityArchiver;    \n    jobsManager.listenActivityArchiver(activityArchiver);\n\n\n    let routingQueueQueued = require('./pubmodules/routing-queue').listenerQueued;\n    winston.debug(\"routingQueueQueued\"); \n    jobsManager.listenRoutingQueue(routingQueueQueued);\n\n    let whatsappQueue = require('@tiledesk/tiledesk-whatsapp-jobworker');\n    winston.info(\"whatsappQueue\");\n    jobsManager.listenWhatsappQueue(whatsappQueue);\n\n    let scheduler = require('./pubmodules/scheduler');    \n    jobsManager.listenScheduler(scheduler);\n\n    let multiWorkerQueue = require('@tiledesk/tiledesk-multi-worker');\n    jobsManager.listenMultiWorker(multiWorkerQueue);\n\n\n    winston.info(\"Jobs started\"); \n\n    await new Promise(function () {});\n    console.log('This text will never be printed');\n}\n\nfunction panic(error)\n{\n    console.error(error);\n    process.exit(1);\n}\n\n// https://stackoverflow.com/a/46916601/1478566\nmain().catch(panic).finally(clearInterval.bind(null, setInterval(a=>a, 1E9)));"
  },
  {
    "path": "jobsManager.js",
    "content": "\nvar winston = require('./config/winston');\n\nclass JobsManager {\n    constructor(jobWorkerEnabled, geoService, botEvent, subscriptionNotifierQueued, botSubscriptionNotifier, updateLeadQueued, updateRequestSnapshotQueued) {\n        this.geoService = geoService;\n        this.botEvent = botEvent;\n        // this.subscriptionNotifier = subscriptionNotifier;\n        this.subscriptionNotifierQueued = subscriptionNotifierQueued;\n        this.botSubscriptionNotifier = botSubscriptionNotifier;\n\n        this.emailNotificatio = undefined;\n        this.activityArchiver = undefined;\n        this.whatsappWorker = undefined;\n        this.multiWorkerQueue = undefined;\n\n        this.jobWorkerEnabled = jobWorkerEnabled;\n        // this.jobWorkerEnabled = false;\n        // if (process.env.JOB_WORKER_ENABLED==\"true\" || process.env.JOB_WORKER_ENABLED == true) {\n        //     this.jobWorkerEnabled = true;\n        // }\n        // winston.info(\"JobsManager jobWorkerEnabled: \"+ this.jobWorkerEnabled);  \n\n        this.updateLeadQueued = updateLeadQueued;\n        this.updateRequestSnapshotQueued = updateRequestSnapshotQueued;\n    }\n\n\n    listen() {      \n        winston.info(\"JobsManager listener started\");  \n        if ( this.jobWorkerEnabled == true) {\n           return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listeners\");  \n        }\n        this.geoService.listen();\n        \n        // this.botEvent.listen(); // disabled\n\n        // this.subscriptionNotifier.start();\n        this.subscriptionNotifierQueued.start();\n\n        this.updateLeadQueued.listen();\n\n        if (this.updateRequestSnapshotQueued) {\n            this.updateRequestSnapshotQueued.listen();\n        }\n\n        // this.botSubscriptionNotifier.start(); // disabled\n    }\n\n    listenEmailNotification(emailNotification) {      \n        winston.info(\"JobsManager listenEmailNotification started\");  \n        if ( this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for Email Notification\");  \n        }\n        this.emailNotification = emailNotification;\n        this.emailNotification.requestNotification.listen();\n    }\n\n    listenRoutingQueue(routingQueue) {\n        winston.info(\"JobsManager routingQueue started\");  \n        if ( this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for routingQueue\");  \n        }\n        this.routingQueue = routingQueue;\n        this.routingQueue.listen();\n    }\n\n    listenScheduler(scheduler) {\n        winston.info(\"JobsManager scheduler started\");  \n        if ( this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for scheduler\");  \n        }\n        this.scheduler = scheduler;\n        this.scheduler.taskRunner.start();    \n    }\n\n    listenActivityArchiver(activityArchiver) {\n        winston.info(\"JobsManager listenActivityArchiver started\"); \n        if ( this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for Activity Archiver\");  \n        } \n        this.activityArchiver = activityArchiver;\n        this.activityArchiver.listen();\n    }\n\n    listenWhatsappQueue(whatsappQueue) {\n        console.log(\"JobsManager listenWhatsappQueue started\");\n        console.log(\"whatsappQueue is: \", whatsappQueue)\n        if ( this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for Whatsapp Queue\");  \n        }\n        // this.whatsappWorker = whatsappQueue;\n        // this.whatsappQueue.listen(); // oppure codice\n    }\n\n    listenMultiWorker(multiWorkerQueue) {\n        console.log(\"JobsManager multiWorkerQueue started\");\n        console.log(\"multiWorkerQueue is: \", multiWorkerQueue)\n        if (this.jobWorkerEnabled == true) {\n            return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for MultiWorker Queue\");  \n        }\n        this.multiWorkerQueue = multiWorkerQueue;\n        this.multiWorkerQueue.startJobsWorker();\n    }\n\n    // listenTrainingQueue(trainingQueue) {\n    //     console.log(\"JobsManager listenTrainingQueue started\");\n    //     console.log(\"trainingQueue is: \", trainingQueue)\n    //     if (this.jobWorkerEnabled == true) {\n    //         return winston.info(\"JobsManager jobWorkerEnabled is enabled. Skipping listener for Training Queue\");  \n    //     }\n    // }\n}\n\n\nmodule.exports = JobsManager;"
  },
  {
    "path": "middleware/fetchLabels.js",
    "content": "\nvar fs = require('fs');\nvar path = require('path');\nvar winston = require('../config/winston');\n\n\nvar labelsDir = __dirname + \"/../config/labels/\";\nwinston.debug('labelsDir: ' + labelsDir);\n\n\nmodule.exports = function (req, res, next) {\n  var filePath = path.join(labelsDir, 'widget.json');\n\n  fs.readFile(filePath, { encoding: 'utf-8' }, function (err, data) {\n    if (err) {\n      winston.error('Error getting labels', err);\n      return res.status(500).send({ success: false, msg: 'Error reading object.' });\n    }\n    winston.debug('label fetched', data);\n\n    // Replace {{ BRAND_NAME }} with process.env.BRAND_NAME value (default value \"Tiledesk\")\n    let brand_name = process.env.BRAND_NAME;\n\n    if (brand_name) {\n      data = data.replaceAll(\"Tiledesk\", brand_name);\n      data = data.replaceAll(\"tiledesk\", brand_name.toLowerCase());\n\n    }\n\n    req.labels = JSON.parse(data);\n    next();\n  });\n}"
  },
  {
    "path": "middleware/file-type.js",
    "content": "const FileType = require('file-type');\nconst fs = require('fs');\n\n// List of text-based MIME types that FileType cannot detect (they don't have binary signatures)\nconst TEXT_MIME_TYPES = [\n  'text/plain',\n  'text/csv',\n  'image/svg+xml',\n  'application/xml',\n  'text/xml'\n];\n\n/**\n * Checks if two MIME types are equivalent, accepting common aliases\n * Examples:\n * - audio/wav === audio/wave === audio/vnd.wave\n * - image/jpeg === image/jpg\n */\nfunction areMimeTypesEquivalent(mimeType1, mimeType2) {\n  if (!mimeType1 || !mimeType2) return false;\n  if (mimeType1 === mimeType2) return true;\n  \n  // Normalize to lowercase for comparison\n  const m1 = mimeType1.toLowerCase();\n  const m2 = mimeType2.toLowerCase();\n  if (m1 === m2) return true;\n  \n  // Common MIME type aliases\n  const aliases = {\n    'audio/wav': ['audio/wave', 'audio/x-wav', 'audio/vnd.wave'],\n    'audio/wave': ['audio/wav', 'audio/x-wav', 'audio/vnd.wave'],\n    'audio/x-wav': ['audio/wav', 'audio/wave', 'audio/vnd.wave'],\n    'audio/vnd.wave': ['audio/wav', 'audio/wave', 'audio/x-wav'],\n    'audio/mpeg': ['audio/opus', 'audio/mp3', 'audio/webm'],\n    'audio/mp3': ['audio/mpeg', 'audio/opus', 'audio/webm'],\n    'audio/opus': ['audio/mpeg', 'audio/mp3', 'audio/webm'],\n    'audio/webm': ['audio/mpeg', 'audio/mp3', 'audio/opus'],\n    'image/jpeg': ['image/jpg'],\n    'image/jpg': ['image/jpeg'],\n    'application/x-zip-compressed': ['application/zip'],\n    'application/zip': ['application/x-zip-compressed'],\n  };\n  \n  // Check if m1 is an alias of m2 or vice versa\n  if (aliases[m1] && aliases[m1].includes(m2)) return true;\n  if (aliases[m2] && aliases[m2].includes(m1)) return true;\n  \n  return false;\n}\n\n// Magic bytes for fallback when file-type throws (e.g. strtok3/token-types Uint8Array vs Buffer)\nconst MAGIC_SIGNATURES = {\n  'video/webm': [[0x1A, 0x45, 0xDF, 0xA3]],           // EBML\n  'audio/webm': [[0x1A, 0x45, 0xDF, 0xA3]],\n  'audio/mpeg': [[0xFF, 0xFB], [0xFF, 0xFA], [0xFF, 0xF3], [0xFF, 0xF2], [0x49, 0x44, 0x33]], // ID3 or MP3 frame\n  'audio/mp3': [[0xFF, 0xFB], [0xFF, 0xFA], [0xFF, 0xF3], [0xFF, 0xF2], [0x49, 0x44, 0x33]],\n  'image/png': [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]],\n  'image/jpeg': [[0xFF, 0xD8, 0xFF]],\n  'image/gif': [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61], [0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],\n  'application/pdf': [[0x25, 0x50, 0x44, 0x46]],\n};\n\nfunction magicMatches(buf, mimetype) {\n  const signatures = MAGIC_SIGNATURES[mimetype && mimetype.toLowerCase()];\n  if (!signatures) return false;\n  for (const sig of signatures) {\n    if (buf.length < sig.length) continue;\n    let ok = true;\n    for (let i = 0; i < sig.length; i++) {\n      const b = buf[i] !== undefined ? (buf[i] & 0xFF) : -1;\n      if (b !== sig[i]) { ok = false; break; }\n    }\n    if (ok) return true;\n  }\n  return false;\n}\n\nconst BASE64_REGEX = /^[A-Za-z0-9+/]+=*$/;\n\n/**\n * Ensures the input is a Node.js Buffer. file-type (and token-types/strtok3) require\n * a Buffer with methods like readUInt8; GridFS or other sources may return\n * Uint8Array, ArrayBuffer, BSON Binary, or (when client sends base64) a string.\n * We always allocate a new Buffer and copy bytes so file-type never receives\n * a buffer-like that loses readUInt8 when sliced (e.g. by strtok3).\n */\nfunction ensureBuffer(buffer) {\n  if (!buffer) return buffer;\n\n  // Base64 string (e.g. client sends form body as base64): decode to binary\n  if (typeof buffer === 'string' && buffer.length > 0) {\n    const trimmed = buffer.replace(/\\s/g, '');\n    if (BASE64_REGEX.test(trimmed)) {\n      return Buffer.from(trimmed, 'base64');\n    }\n    return Buffer.from(buffer, 'utf8');\n  }\n\n  // Copy into a new Buffer so file-type's internal slices are always real Buffers\n  let uint8;\n  if (buffer instanceof Uint8Array) {\n    uint8 = buffer;\n  } else if (buffer instanceof ArrayBuffer) {\n    uint8 = new Uint8Array(buffer);\n  } else if (buffer && typeof buffer.buffer === 'object' && buffer.buffer instanceof ArrayBuffer) {\n    uint8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n  } else if (Buffer.isBuffer(buffer)) {\n    uint8 = new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength);\n  } else {\n    uint8 = new Uint8Array(Buffer.from(buffer));\n  }\n  return Buffer.from(uint8);\n}\n\nasync function verifyFileContent(buffer, mimetype) {\n  if (!buffer) throw new Error(\"No file provided\");\n\n  const buf = ensureBuffer(buffer);\n\n  let fileType;\n  try {\n    fileType = await FileType.fromBuffer(buf);\n  } catch (err) {\n    // strtok3 uses Uint8Array for numBuffer but token-types expects Buffer.readUInt8 (known compat bug in deps)\n    if (err && typeof err.message === 'string' && err.message.includes('readUInt8')) {\n      if (mimetype && magicMatches(buf, mimetype)) return true;\n      const err2 = new Error(`File content could not be verified. Declared mimetype: ${mimetype}`);\n      err2.source = \"FileContentVerification\";\n      throw err2;\n    }\n    throw err;\n  }\n\n  // If FileType couldn't detect the file type (returns null/undefined)\n  if (!fileType) {\n    // For text-based MIME types, accept the declared mimetype since FileType can't detect them\n    if (mimetype && TEXT_MIME_TYPES.includes(mimetype)) {\n      try {\n        buf.toString('utf8');\n        return true;\n      } catch (e) {\n        const err = new Error(`File content is not valid text for mimetype: ${mimetype}`);\n        err.source = \"FileContentVerification\";\n        throw err;\n      }\n    }\n    if (mimetype && mimetype.startsWith('image/svg')) {\n      try {\n        buf.toString('utf8');\n        return true;\n      } catch (e) {\n        const err = new Error(`File content is not valid text for mimetype: ${mimetype}`);\n        err.source = \"FileContentVerification\";\n        throw err;\n      }\n    }\n    if (mimetype && magicMatches(buf, mimetype)) return true;\n    const err = new Error(`File content does not match mimetype. Detected: unknown, provided: ${mimetype}`);\n    err.source = \"FileContentVerification\";\n    throw err;\n  }\n\n  // If FileType detected a type, it must match the declared mimetype (or be equivalent)\n  if (mimetype && !areMimeTypesEquivalent(fileType.mime, mimetype)) {\n    const err = new Error(`File content does not match mimetype. Detected: ${fileType.mime}, provided: ${mimetype}`);\n    err.source = \"FileContentVerification\";\n    throw err;\n  }\n\n  return true;\n}\n\nmodule.exports = verifyFileContent;\n"
  },
  {
    "path": "middleware/has-role.js",
    "content": "var Faq_kb = require(\"../models/faq_kb\");\nvar Subscription = require(\"../models/subscription\");\nvar winston = require('../config/winston');\n\nvar projectUserService = require(\"../services/projectUserService\");\nclass RoleChecker {\n    \n\n    \n    constructor() {\n      winston.debug(\"RoleChecker\");\n\n      this.ROLES =  {\n            \"guest\":        [\"guest\"],\n            \"user\":         [\"guest\",\"user\"],\n            \"teammate\":     [\"guest\",\"user\",\"teammate\"],\n            \"agent\":        [\"guest\",\"user\",\"teammate\",\"agent\"],\n            \"supervisor\":   [\"guest\",\"user\",\"teammate\",\"agent\",\"supervisor\"],\n            \"admin\":        [\"guest\",\"user\",\"teammate\",\"agent\", \"supervisor\", \"admin\"],\n            \"owner\":        [\"guest\",\"user\",\"teammate\",\"agent\", \"supervisor\", \"admin\", \"owner\"],\n      }\n    }\n      \n    isType(type) {        \n      var that = this;   \n        winston.debug(\"isType\",isType);\n        return function(req, res, next) {\n          if (that.isTypeAsFunction(type)) {\n            return next();\n          } else {\n            winston.error('isType type not supported.');\n            return next({success: false, msg: 'type not supported.'});\n          }\n        }\n      }\n\n\n\n      isTypeAsFunction(type, user) {                 \n            winston.debug(\"isType:\"+type);\n            winston.debug(\"user\", user);\n           //TODO Check if belongs to project\n            if (type=='subscription' && user instanceof Subscription){\n              winston.debug(\"isTypeAsFunction is subscription\");\n              return true\n            } else if (type=='bot' && user instanceof Faq_kb){\n              winston.debug(\"isTypeAsFunction is bot\");\n              return true\n            } else {\n              winston.debug(\"isTypeAsFunction is false\");\n\n              var adminEmail = process.env.ADMIN_EMAIL || \"admin@tiledesk.com\";\n\n              if (user && user.email && user.email === adminEmail) { //skip has role check \n                return true;\n              }\n              return false;\n            }\n       }\n\n\n      isTypesAsFunction(types, user) {                 \n        winston.debug(\"isTypes:\"+types);\n        winston.debug(\"user\", user);\n        var isType = false;\n        var BreakException = {};\n\n        if (types && types.length>0) {\n          try {\n            types.forEach(type => {\n              winston.debug(\"type:\"+type);\n              isType = this.isTypeAsFunction(type, user);\n              winston.debug(\"isType:\"+ isType);\n              if (isType==true) {\n                throw BreakException; //https://stackoverflow.com/questions/2641347/short-circuit-array-foreach-like-calling-break\n              }\n            });\n\n          } catch (e) {\n            if (e !== BreakException) throw e;\n          }\n        }\n\n        return isType;\n      }    \n        \n  \n      hasPermission(permission) {\n        var that = this;\n        return async(req, res, next) => {\n          if (!req.params.projectid && !req.projectid) {\n            return res.status(400).send({success: false, msg: 'req.params.projectid is not defined.'});\n          }\n\n          let projectid = req.params.projectid || req.projectid;\n\n          var project_user = req.projectuser;\n          winston.debug(\"hasPermission project_user hasPermission\", project_user);\n\n          if (!project_user) {\n\n            try {\n              project_user = await projectUserService.getWithPermissions(req.user._id, projectid, req.user.sub);            \n            } catch(err) {\n              winston.error(\"Error getting project_user for hasrole\",err);\n              return next(err);\n            }\n            winston.debug(\"project_user: \", JSON.stringify(project_user));\n          }     \n\n          if (project_user) {\n            \n            req.projectuser = project_user;\n            winston.debug(\"hasPermission req.projectuser\", req.projectuser);\n\n            var permissions = project_user._doc.rolePermissions;\n           \n            winston.debug(\"hasPermission permissions\", permissions);\n  \n            winston.debug(\"hasPermission permission: \"+ permission);\n\n            if (permission==undefined) {\n                winston.debug(\"permission is empty go next\");\n                return next();\n            }\n\n            if (permissions!=undefined && permissions.length>0) {\n              if (permissions.includes(permission)) {\n                next();\n              }else {                         \n                res.status(403).send({success: false, msg: 'you dont have the required permission.'});                \n              }\n            } else { \n              res.status(403).send({success: false, msg: 'you dont have the required permission. Is is empty'});                       \n            }\n          } else {\n          \n            /**\n            * Updated by Johnny - 29mar2024 - START\n            */\n            // console.log(\"req.user: \", req.user);\n            if (req.user.email === process.env.ADMIN_EMAIL) {\n              req.user.attributes = { isSuperadmin: true };\n              next();\n            } else {\n              res.status(403).send({success: false, msg: 'you dont belong to the project.'});\n            }\n            /**\n            * Updated by Johnny - 29mar2024 - END\n            */\n          \n          }\n       \n       \n         \n          \n        }\n      }\n      \n\n      hasRole(role) {\n        return this.hasRoleOrTypes(role);\n      }\n\n       hasRoleOrTypes(role, types, permission) {\n        // console.log(\"hasRoleOrTypes\",role,types);\n\n        var that = this;\n\n        // winston.debug(\"HasRole\");\n        return async(req, res, next) => {\n          \n          // winston.debug(\"req.originalUrl\" + req.originalUrl);\n          // winston.debug(\"req.params\" + JSON.stringify(req.params));\n\n        // same route doesnt contains projectid so you can't use that\n          // winston.debug(\"req.params.projectid: \" + req.params.projectid);\n          if (!req.params.projectid && !req.projectid) {\n            return res.status(400).send({success: false, msg: 'req.params.projectid is not defined.'});\n          }\n\n          let projectid = req.params.projectid || req.projectid;\n\n          //  winston.info(\"req.user._id: \" + req.user._id);\n\n          // winston.info(\"req.projectuser: \" + req.projectuser);\n          //winston.debug(\"req.user\", req.user);\n          //winston.debug(\"role\", role);\n      \n\n          // console.log(\"QUIIIIIIIIIIIIIIIIIIIIIII\",type);\n          if (types && types.length>0) {\n            // console.log(\"QUIIIIIIIIIIIIIIIIIIIIIII\");\n            var checkRes = that.isTypesAsFunction(types, req.user);\n            winston.debug(\"checkRes: \" + checkRes);\n\n            if (checkRes) {\n             return next();\n            }            \n            // return that.isType(type)(req,res,next);\n            // console.log(\"typers\",typers);\n          }\n\n          // if (!req.user._id) {\n          //   res.status(403).send({success: false, msg: 'req.user._id not defined.'});\n          // }\n          winston.debug(\"hasRoleOrType req.user._id \" +req.user._id);\n          // project_user_qui_importante         \n\n          var project_user = req.projectuser;\n          winston.debug(\"hasRoleOrTypes project_user hasRoleOrTypes\", project_user);\n\n          if (!project_user) {\n            winston.debug(\"load project_user\");\n            try {\n              project_user = await projectUserService.getWithPermissions(req.user._id, projectid, req.user.sub);            \n            } catch(err) {\n              winston.error(\"Error getting project_user for hasrole\",err);\n              return next(err);\n            }\n                    \n            winston.debug(\"project_user: \", JSON.stringify(project_user));\n            \n          }\n\n          if (project_user) {\n            \n            req.projectuser = project_user;\n            winston.debug(\"hasRoleOrTypes req.projectuser\", req.projectuser.toJSON());\n\n            var userRole = project_user.role;\n            winston.debug(\"userRole\", userRole);\n\n  \n            if (!role) { //????\n              next();\n            }else {\n  \n              var hierarchicalRoles = that.ROLES[userRole];\n              winston.debug(\"hierarchicalRoles\", hierarchicalRoles);\n  \n              if ( hierarchicalRoles ) {  //standard role\n                winston.debug(\"standard role: \"+ userRole);\n                if (hierarchicalRoles.includes(role)) {\n                  next();\n                } else {\n                  res.status(403).send({success: false, msg: 'you dont have the required role.'});\n                }\n              } else { //custom role\n                \n                winston.debug(\"custom role: \"+ userRole);\n                return that.hasPermission(permission)(req, res, next);\n\n                // if (permission==undefined) {\n                //   winston.debug(\"permission is empty go next\");\n                //   return next();\n                // }\n\n                // // invece di questo codice richiama hasPermission()\n                // var permissions = project_user._doc.rolePermissions;\n           \n                // winston.debug(\"hasPermission permissions\", permissions);\n      \n                // winston.debug(\"hasPermission permission: \"+ permission);               \n\n                // if (permissions!=undefined && permissions.length>0) {\n                //   if (permissions.includes(permission)) {\n                //     next();\n                //   }else {                         \n                //     res.status(403).send({success: false, msg: 'you dont have the required permission.'});                \n                //   }\n                // } else { \n                //   res.status(403).send({success: false, msg: 'you dont have the required permission. Is is empty'});                       \n                // }\n\n\n              }\n                \n            }\n          \n          } else {\n          \n            /**\n             * Updated by Johnny - 29mar2024 - START\n             */\n            // console.log(\"req.user: \", req.user);\n            if (req.user.email === process.env.ADMIN_EMAIL) {\n              req.user.attributes = { isSuperadmin: true };\n              next();\n            } else {\n              res.status(403).send({success: false, msg: 'you dont belong to the project.'});\n            }\n            /**\n             * Updated by Johnny - 29mar2024 - END\n             */\n\n            // if (req.user) equals super admin next()\n            //res.status(403).send({success: false, msg: 'you dont belong to the project.'});\n          }\n      \n      \n        }\n      }\n        \n}\n\n\n\nvar roleChecker = new RoleChecker();\nmodule.exports = roleChecker;\n\n\n"
  },
  {
    "path": "middleware/ipFilter.js",
    "content": "const ipfilter = require('express-ipfilter').IpFilter\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\n\n\nvar customDetection = function (req)  {\n    // const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;  \n    // const ip = (req.headers['x-forwarded-for'] || '').split(',').pop().trim() ||        //https://stackoverflow.com/questions/8107856/how-to-determine-a-users-ip-address-in-node\n    //   req.socket.remoteAddress\n  \n  \n    let ip = req.socket.remoteAddress;\n  \n      const xFor =  req.headers['x-forwarded-for'];\n      if (xFor ) {\n        const xForArr = xFor.split(',');\n        if (xForArr && xForArr.length>0) {\n          ip = xForArr.shift();\n        }\n      }\n      // const ip = \n      // req.headers['x-forwarded-for']?.split(',').shift()\n      // || req.socket?.remoteAddress\n  \n    winston.info(\"standard ip: \"+ip); // ip address of the user\n    return ip;\n}\n\nvar getToken = function (headers) {\n  winston.debug(\"getToken\",headers);\n  if (headers && headers.authorization) {\n    var parted = headers.authorization.split(' ');\n    if (parted.length === 2) {\n      return parted[1];\n    } else {\n      return null;\n    }\n  } else {\n    return null;\n  }\n};\n  \n\nclass IPFilter {\n    \n\n    \n    constructor() {\n    }\n\n\n\n\n\n\n  \n  \n  \nprojectIpFilter (req, res, next) {\n    var that = this;\n    // console.log(\"that\", that)\n  \n    const nextIp = function(err) {\n      winston.debug(\"projectIpFilter next\",err);\n    \n        if (err && err.name === \"IpDeniedError\") {\n          winston.info(\"IpDeniedError for projectIpFilter\");\n          return res.status(401).json({ err: \"error project ip filter\" });\n          // next(err) \n        } \n    \n      next();\n    \n    }\n  \n  \n    if (!req.project) {\n      return next();\n    }\n    \n    var projectIpFilterEnabled = req.project.ipFilterEnabled;\n    winston.debug(\"project projectIpFilterEnabled: \" +projectIpFilterEnabled)\n  \n    var projectIpFilter =  req.project.ipFilter\n    winston.debug(\"project ipFilter: \" + projectIpFilter)\n    \n    if (projectIpFilterEnabled === true && projectIpFilter && projectIpFilter.length > 0) {\n      winston.debug(\"filtering project IpFilter with \", projectIpFilter );\n      var ip = ipfilter(projectIpFilter, { detectIp: customDetection, mode: 'allow' })\n      // var ip = ipfilter(projectIpFilter, { mode: 'allow' })\n       ip(req, res, nextIp);\n    } else {\n      next();\n    }\n  \n  }\n  \n   projectIpFilterDeny (req, res, next) {\n  \n    const nextIp = function(err) {\n      winston.debug(\"projectIpFilter next\",err);\n    \n        if (err && err.name === \"IpDeniedError\") {\n          winston.info(\"IpDeniedError for projectIpFilterDeny\");\n          return res.status(401).json({ err: \"error project deny ip filter\" });\n          // next(err) \n        } \n    \n      next();\n    \n    }\n  \n    if (!req.project) {\n      return next();\n    }\n    \n    var projectIpFilterDenyEnabled = req.project.ipFilterDenyEnabled;\n    winston.debug(\"project projectIpFilterDenyEnabled: \" +projectIpFilterDenyEnabled)\n  \n    var projectIpFilterDeny =  req.project.ipFilterDeny\n    winston.debug(\"project IpFilterDeny: \" + projectIpFilterDeny)\n  \n  \n    if (projectIpFilterDenyEnabled === true && projectIpFilterDeny && projectIpFilterDeny.length > 0) {\n      winston.debug(\"filtering project projectIpFilterDeny with \", projectIpFilterDeny );\n      var ip = ipfilter(projectIpFilterDeny, { detectIp: customDetection, mode: 'deny' })\n      ip(req, res, nextIp);\n    } else {\n      next();\n    }\n  \n  }\n  \n  \n  \nprojectBanUserFilter(req, res, next) {\n  \n  winston.debug(\"projectBanUserFilter hereee*********** \")\n\n    const nextIp = function(err) {\n      winston.debug(\"projectBanUserFilter next\",err);\n    \n        if (err && err.name === \"IpDeniedError\") {\n          winston.info(\"IpDeniedError for projectBanUserFilter\");\n          return res.status(401).json({ err: \"error projectBanUserFilter\" });\n          // next(err) \n        } \n    \n      next();\n    \n    }\n  \n    if (!req.project) {\n      return next();\n    }\n    \n    var bannedUsers =  req.project.bannedUsers\n    winston.debug(\"project bannedUsers: \" + bannedUsers)\n  \n    if (bannedUsers && bannedUsers.length > 0) {\n  \n      let bannedUsersArr = [];\n      let bannedUsersIdUserArr = [];\n      for (var i =0; i < bannedUsers.length; i++) {\n        bannedUsersArr.push(bannedUsers[i].ip);\n        bannedUsersIdUserArr.push(bannedUsers[i].id);\n      }\n    \n      winston.debug(\"project req.preDecodedJwt: \", req.preDecodedJwt)\n      // winston.debug(\"project req.preDecodedJwt._id: \"+ req.preDecodedJwt._id)\n\n\n      if (req.preDecodedJwt && req.preDecodedJwt._id && bannedUsersIdUserArr.indexOf(req.preDecodedJwt._id) > -1) {\n        winston.info(\"filtering project bannedUsers with id: \" + req.preDecodedJwt._id)\n        return res.status(401).json({ err: \"error projectBanUserFilter by id\" });\n      }\n\n\n      // winston.debug(\"filtering project bannedUsers with \", bannedUsersArr );\n      // var ip = ipfilter(bannedUsersArr, { detectIp: customDetection, mode: 'deny' })\n      // ip(req, res, nextIp);\n      next();\n    } else {\n      next();\n    }\n  \n  }\n  \n  \n\n  \n\n  decodeJwt(req, res, next) {\n  \n    let token = getToken(req.headers);\n    winston.debug(\"filtering token \" + token); \n\n    if (token) {\n\n      try {\n        var decoded = jwt.decode(token);\n        winston.debug(\"filtering decoded \", decoded);\n        req.preDecodedJwt = decoded;\n      }catch(e) {\n        winston.debug(\"Error decoding jwt\");\n      }\n     \n    }\n   \n    \n    next();\n  }\n  \n  \n\n\n  \n}\nvar iPFilter = new IPFilter();\nmodule.exports = iPFilter;"
  },
  {
    "path": "middleware/noentitycheck.js",
    "content": "var winston = require('../config/winston');\n\n\n\nmodule.exports = \n    function(req,res,next){ \n        winston.debug(\"before disablePassportEntityCheck=true\");\n        req.disablePassportEntityCheck = true;\n        winston.debug(\"disablePassportEntityCheck=true\"); \n        next();\n}"
  },
  {
    "path": "middleware/passport.js",
    "content": "var passportJWT = require(\"passport-jwt\");\nvar JwtStrategy = passportJWT.Strategy;\nvar ExtractJwt = passportJWT.ExtractJwt;\n\nvar passportHttp = require(\"passport-http\");\nvar BasicStrategy = passportHttp.BasicStrategy;\nvar GoogleStrategy = require('passport-google-oidc');\n\nvar winston = require('../config/winston');\n// var AnonymousStrategy = require('passport-anonymous').Strategy;\n\n// load up the user model\nvar User = require('../models/user');\nvar config = require('../config/database'); // get db config file\nvar Faq_kb = require(\"../models/faq_kb\");\nvar Project = require('../models/project');\nvar Subscription = require('../models/subscription');\n\nvar Auth = require('../models/auth');\nvar userService = require('../services/userService');\n\nvar UserUtil = require('../utils/userUtil');\nvar jwt = require('jsonwebtoken');\nconst url = require('url');\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\nvar uniqid = require('uniqid');\n\n\nconst MaskData = require(\"maskdata\");\n\nconst maskOptions = {\n    // Character to mask the data. default value is '*'\n    maskWith: \"*\",\n    // If the starting 'n' digits needs to be unmasked\n    // Default value is 4\n    unmaskedStartDigits: 3, //Should be positive Integer\n    //If the ending 'n' digits needs to be unmasked\n    // Default value is 1\n    unmaskedEndDigits: 3 // Should be positive Integer\n};\n\nvar alg = process.env.GLOBAL_SECRET_ALGORITHM;\nwinston.info('Authentication Global Algorithm : ' + alg);\n\n// TODO STAMPA ANCHE PUBLIC\n\nvar configSecret = process.env.GLOBAL_SECRET || config.secret;\n\nvar pKey = process.env.GLOBAL_SECRET_OR_PUB_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n    configSecret = pKey.replace(/\\\\n/g, '\\n');\n}\n// console.log(\"configSecret\",configSecret);\n// if (process.env.GLOBAL_SECRET_OR_PUB_KEY) {\n//   console.log(\"GLOBAL_SECRET_OR_PUB_KEY defined\");\n\n// }else {\n//   console.log(\"GLOBAL_SECRET_OR_PUB_KEY undefined\");\n// }\n\nvar maskedconfigSecret = MaskData.maskPhone(configSecret, maskOptions);\nwinston.info('Authentication Global Secret : ' + maskedconfigSecret);\n\nvar enableGoogleSignin = false;\nif (process.env.GOOGLE_SIGNIN_ENABLED == \"true\" || process.env.GOOGLE_SIGNIN_ENABLED == true) {\n    enableGoogleSignin = true;\n}\nwinston.info('Authentication Google Signin enabled : ' + enableGoogleSignin);\n\n\nvar enableOauth2Signin = false;\nif (process.env.OAUTH2_SIGNIN_ENABLED == \"true\" || process.env.OAUTH2_SIGNIN_ENABLED == true) {\n    enableOauth2Signin = true;\n}\nwinston.info('Authentication Oauth2 Signin enabled : ' + enableOauth2Signin);\n\n\nvar jwthistory = undefined;\ntry {\n  jwthistory = require('@tiledesk-ent/tiledesk-server-jwthistory');\n} catch(err) {\n  winston.info(\"jwthistory not present\", err);\n}\n\nlet JWT_HISTORY_ENABLED = false;\nif (process.env.JWT_HISTORY_ENABLED==true || process.env.JWT_HISTORY_ENABLED==\"true\") {\n  JWT_HISTORY_ENABLED = true;\n}\nwinston.debug(\"JWT_HISTORY_ENABLED: \" + JWT_HISTORY_ENABLED);\n\n\n\nmodule.exports = function(passport) {\n    // passport.serializeUser(function(user, done) {\n    //     console.log(\"serializeUser\");\n\n    //     done(null, user);\n    //   });\n\n    //   passport.deserializeUser(function(user, done) {\n    //     done(null, user);\n    //   });\n\n    var opts = {\n        // jwtFromRequest: ExtractJwt.fromAuthHeader(),\n        jwtFromRequest: ExtractJwt.fromExtractors([ExtractJwt.fromAuthHeaderWithScheme(\"jwt\"), ExtractJwt.fromUrlQueryParameter('secret_token')]),\n        //this will help you to pass request body to passport\n        passReqToCallback: true, //https://stackoverflow.com/questions/55163015/how-to-bind-or-pass-req-parameter-to-passport-js-jwt-strategy\n        // secretOrKey: configSecret,\n        secretOrKeyProvider: function (request, rawJwtToken, done) {\n            // winston.info(\"secretOrKeyProvider \", request );\n\n            // if (request.project) {\n            //   winston.info(\"secretOrKeyProvider.request.project.jwtSecret: \"+request.project.jwtSecret );\n            // }\n\n            // winston.info(\"secretOrKeyProvider: \"+request.project.name );\n            // winston.info(\"secretOrKeyProvider: \"+rawJwtToken );\n\n            var decoded = request.preDecodedJwt\n            winston.debug(\"decoded: \", decoded);\n            if (!decoded) { //fallback\n                winston.debug(\"load decoded after: \");\n                decoded = jwt.decode(rawJwtToken);\n            }\n\n            winston.debug(\"decoded after: \", decoded);\n\n            // qui arriva questo \n            // decoded:  {\"_id\":\"5ce3ee855c520200176c189e\",\"updatedAt\":\"2019-05-31T09:50:22.949Z\",\"createdAt\":\"2019-05-21T12:26:45.192Z\",\"name\":\"botext\",\"url\":\"https://tiledesk-v2-simple--andrealeo83.repl.co\",\"id_project\":\"5ce3d1ceb25ad30017274bc5\",\"trashed\":false,\"createdBy\":\"5ce3d1c7b25ad30017274bc2\",\"__v\":0,\"external\":true,\"iat\":1559297130,\"aud\":\"https://tiledesk.com\",\"iss\":\"https://tiledesk.com\",\"sub\":\"5ce3ee855c520200176c189e@tiledesk.com/bot\"}\n\n\n            if (decoded && decoded.aud) {\n\n                winston.debug(\"decoded.aud: \" + decoded.aud);\n\n\n                const audUrl = new URL(decoded.aud);\n                winston.debug(\"audUrl: \" + audUrl);\n                const path = audUrl.pathname;\n                winston.debug(\"audUrl path: \" + path);\n\n                const AudienceType = path.split(\"/\")[1];\n                winston.debug(\"audUrl AudienceType: \" + AudienceType);\n\n                const AudienceId = path.split(\"/\")[2];\n                winston.debug(\"audUrl AudienceId: \" + AudienceId);\n\n                if (AudienceType == \"bots\") {\n\n                    if (!AudienceId) {\n                        winston.error(\"AudienceId for bots is required: \", decoded);\n                        return done(null, null);\n                    }\n\n                    winston.debug(\"bot id AudienceId: \" + AudienceId);\n                    let qbot = Faq_kb.findById(AudienceId).select('+secret');\n\n                    if (cacheEnabler.faq_kb) {\n                        let id_project = decoded.id_project;\n                        winston.debug(\"decoded.id_project:\" + decoded.id_project);\n                        qbot.cache(cacheUtil.defaultTTL, id_project + \":faq_kbs:id:\" + AudienceId + \":secret\")\n                        winston.debug('faq_kb AudienceId cache enabled');\n                    }\n\n\n                    qbot.exec(function (err, faq_kb) { //TODO add cache_bot_here\n                        if (err) {\n                            winston.error(\"auth Faq_kb err: \", {error: err, decoded: decoded});\n                            return done(null, null);\n                        }\n                        if (!faq_kb) {\n                            winston.warn(\"faq_kb not found with id: \" + AudienceId, decoded);\n                            return done(null, null);\n                        }\n\n                        winston.debug(\"faq_kb: \", faq_kb);\n                        // winston.debug(\"faq_kb.secret: \"+ faq_kb.secret );\n                        done(null, faq_kb.secret);\n                    });\n                } else if (AudienceType == \"projects\") {\n                    if (!AudienceId) {\n                        winston.error(\"AudienceId for projects is required: \", decoded);\n                        return done(null, null);\n                    }\n\n                    winston.debug(\"project id: \" + AudienceId);\n                    Project.findOne({_id: AudienceId, status: 100}).select('+jwtSecret')\n                        //@DISABLED_CACHE .cache(cacheUtil.queryTTL, \"projects:query:id:status:100:\"+AudienceId+\":select:+jwtSecret\") //project_cache\n                        .exec(function (err, project) {\n                            if (err) {\n                                winston.error(\"auth Project err: \", {error: err, decoded: decoded});\n                                return done(null, null);\n                            }\n                            if (!project) {\n                                winston.warn(\"Project not found with id: \" + AudienceId, decoded);\n                                return done(null, null);\n                            }\n                            winston.debug(\"project: \", project);\n                            winston.debug(\"project.jwtSecret: \" + project.jwtSecret);\n                            done(null, project.jwtSecret);\n                        });\n\n                } else if (AudienceType == \"subscriptions\") {\n\n                    if (!AudienceId) {\n                        winston.error(\"AudienceId for subscriptions is required: \", decoded);\n                        return done(null, null);\n                    }\n\n                    winston.debug(\"Subscription id: \" + AudienceId);\n                    Subscription.findById(AudienceId).select('+secret').exec(function (err, subscription) {\n                        if (err) {\n                            winston.error(\"auth Subscription err: \", {error: err, decoded: decoded});\n                            return done(null, null);\n                        }\n                        if (!subscription) {\n                            winston.warn(\"subscription not found with id: \" + AudienceId, decoded);\n                            return done(null, null);\n                        }\n                        winston.debug(\"subscription: \", subscription);\n                        winston.debug(\"subscription.secret: \" + subscription.secret);\n                        done(null, subscription.secret);\n                    });\n                } else if (decoded.aud == \"https://tiledesk.com\") {\n                    winston.debug(\"configSecret: \" + maskedconfigSecret);\n                    done(null, configSecret); //pub_jwt pp_jwt \n                } else {\n                    winston.debug(\"configSecret: \" + maskedconfigSecret);\n                    done(null, configSecret); //pub_jwt pp_jwt\n                }\n            } else {\n                winston.debug(\"configSecret: \" + maskedconfigSecret);\n                done(null, configSecret); //pub_jwt pp_jwt\n            }\n        }\n    };\n\n\n  winston.debug(\"passport opts: \", opts);\n\n  passport.use(new JwtStrategy(opts, async(req, jwt_payload, done)  => {\n  // passport.use(new JwtStrategy(opts, function(req, jwt_payload, done) {\n    winston.debug(\"jwt_payload\",jwt_payload);\n    // console.log(\"req\",req);\n    \n\n    // console.log(\"jwt_payload._doc._id\",jwt_payload._doc._id);\n\n\n    if (jwt_payload._id == undefined  && (jwt_payload._doc == undefined || (jwt_payload._doc && jwt_payload._doc._id==undefined))) {\n      var err = \"jwt_payload._id or jwt_payload._doc._id can t be undefined\" ;\n      winston.error(err);\n      return done(null, false);\n    }\n                                                            //JWT OLD format\n     const identifier = jwt_payload._id || jwt_payload._doc._id;\n    \n    // const subject = jwt_payload.sub || jwt_payload._id || jwt_payload._doc._id;\n    winston.debug(\"passport identifier: \" + identifier);\n\n    const subject = jwt_payload.sub;\n    winston.debug(\"passport subject: \" + subject);\n\n    winston.debug(\"passport identifier: \" + identifier + \" subject \" + subject);\n\n    var fullUrl = req.protocol + '://' + req.get('host') + req.originalUrl;\n    winston.debug(\"fullUrl:\"+ fullUrl);\n\n    winston.debug(\"req.disablePassportEntityCheck:\"+req.disablePassportEntityCheck);\n\n    if (req && req.disablePassportEntityCheck) { //req can be null\n      // jwt_payload.id = jwt_payload._id; //often req.user.id is used inside code. req.user.id  is a mongoose getter of _id\n      // is better to rename req.user.id to req.user._id in all files\n      winston.debug(\"req.disablePassportEntityCheck enabled\");\n      return done(null, jwt_payload);\n    }\n    winston.debug(\"jwthistory passport\",jwthistory);\n\n    //TODO check into DB if JWT is revoked \n    if (jwthistory && JWT_HISTORY_ENABLED==true) {\n      var jwtRevoked = await jwthistory.isJWTRevoked(jwt_payload.jti);\n      winston.debug(\"passport jwt jwtRevoked: \"+ jwtRevoked);\n      if (jwtRevoked) {\n        winston.warn(\"passport jwt is revoked with jti: \"+ jwt_payload.jti);\n        return done(null, false);\n      }\n    }\n\n    if (subject == \"bot\") {\n      winston.debug(\"Passport JWT bot\");\n\n        let qbot = Faq_kb.findOne({_id: identifier}); //TODO add cache_bot_here\n\n          if (cacheEnabler.faq_kb) {\n            let id_project = jwt_payload.id_project;\n            winston.debug(\"jwt_payload.id_project:\"+jwt_payload.id_project);\n            qbot.cache(cacheUtil.defaultTTL, id_project+\":faq_kbs:id:\"+identifier)\n            winston.debug('faq_kb cache enabled');\n          }\n  \n          qbot.exec(function(err, faq_kb) {\n\n          if (err) {\n            winston.error(\"Passport JWT bot err\", err);\n            return done(err, false);\n          }\n          if (faq_kb) {\n            winston.debug(\"Passport JWT bot user\", faq_kb);\n            return done(null, faq_kb);\n          } else {\n            winston.warn(\"Passport JWT bot not user\");\n            return done(null, false);\n          }\n      });\n    // } else if (subject==\"projects\") {      \n\n    } else if (subject==\"subscription\") {\n    \n      Subscription.findOne({_id: identifier}, function(err, subscription) {\n        if (err) {\n          winston.error(\"Passport JWT subscription err\", err);\n          return done(err, false);\n        }\n        if (subscription) {\n          winston.debug(\"Passport JWT subscription user\", subscription);\n          return done(null, subscription);\n        } else {\n          winston.warn(\"Passport JWT subscription not user\", subscription);\n          return done(null, false);\n        }\n      });              \n\n    } else if (subject==\"userexternal\") {\n    \n     \n        if (jwt_payload) {\n\n          // const audUrl  = new URL(jwt_payload.aud);\n          // winston.info(\"audUrl: \"+ audUrl );\n\n          // const path = audUrl.pathname;\n          // winston.info(\"audUrl path: \" + path );\n          \n          // const AudienceType = path.split(\"/\")[1];\n          // winston.info(\"audUrl AudienceType: \" + AudienceType );\n\n          // const AudienceId = path.split(\"/\")[2];\n          // winston.info(\"audUrl AudienceId: \" + AudienceId );\n\n          // jwt_payload._id = AudienceId + \"-\" + jwt_payload._id;\n          \n\n\n          winston.debug(\"Passport JWT userexternal\", jwt_payload);\n          var userM = UserUtil.decorateUser(jwt_payload);\n          winston.debug(\"Passport JWT userexternal userM\", userM);\n\n          return done(null, userM );\n        }  else {\n          var err = {msg: \"No jwt_payload passed. Its required\"};\n          winston.error(\"Passport JWT userexternal err\", err);\n          return done(err, false);\n        }                \n\n     } else if (subject==\"guest\") {\n    \n     \n        if (jwt_payload) {\n          winston.debug(\"Passport JWT guest\", jwt_payload);\n          var userM = UserUtil.decorateUser(jwt_payload);\n          winston.debug(\"Passport JWT guest userM\", userM);\n          return done(null, userM );\n        }  else {\n          var err = {msg: \"No jwt_payload passed. Its required\"};\n          winston.error(\"Passport JWT guest err\", err);\n          return done(err, false);\n        }                   \n\n    } else {\n      winston.debug(\"Passport JWT generic user\");\n      let quser = User.findOne({_id: identifier, status: 100})   //TODO user_cache_here\n        //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+identifier)\n\n        if (cacheEnabler.user) {\n          quser.cache(cacheUtil.defaultTTL, \"users:id:\"+identifier)\n          winston.debug('user cache enabled');\n        }\n\n        quser.exec(function(err, user) {\n          if (err) {\n            winston.error(\"Passport JWT generic err \", err);\n            return done(err, false);\n          }\n          if (user) {\n            winston.debug(\"Passport JWT generic user \", user);\n            return done(null, user);\n          } else {\n            winston.debug(\"Passport JWT generic not user\");\n            return done(null, false);\n          }\n      });\n\n    }\n   \n\n\n  }));\n\n\n\n  passport.use(new BasicStrategy(function(userid, password, done) {\n      \n      winston.debug(\"BasicStrategy: \" + userid);\n      \n\n      var email = userid.toLowerCase();\n      winston.debug(\"email lowercase: \" + email);\n\n      User.findOne({ email: email, status: 100}, 'email firstname lastname password emailverified id') //TODO user_cache_here. NOT used frequently. ma attento select. ATTENTO QUI NN USEREI LA SELECT altrimenti con JWT ho tuttto USER mentre con basich auth solo aluni campi\n      //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:email:\"+email)\n      .exec(function (err, user) {\n       \n        if (err) {\n            // console.log(\"BasicStrategy err.stop\");\n            return done(err); \n        }\n        if (!user) { return done(null, false); }\n        \n        user.comparePassword(password, function (err, isMatch) {\n            if (isMatch && !err) {\n\n              // if user is found and password is right create a token\n              // console.log(\"BasicStrategy ok\");\n              return done(null, user);\n\n            } else {\n                return done(err);\n            }\n        });\n      });\n  }));\n\n\n    if (enableGoogleSignin == true) {\n        let googleClientId = process.env.GOOGLE_CLIENT_ID;\n        let googleClientSecret = process.env.GOOGLE_CLIENT_SECRET;\n        let googleCallbackURL = process.env.GOOGLE_CALLBACK_URL || \"http://localhost:3000/auth/google/callback\";\n\n        winston.info(\"Enabling Google Signin strategy with ClientId: \" + googleClientId + \" callbackURL: \" + googleCallbackURL + \" clientSecret: \" + googleClientSecret);\n\n        passport.use(new GoogleStrategy({\n                clientID: googleClientId,\n                clientSecret: googleClientSecret,\n                callbackURL: googleCallbackURL,\n            },\n            async function (issuer, profile, cb) {\n                try {\n                    winston.debug(\"issuer: \" + issuer);\n                    winston.debug(\"profile\", profile);\n\n                    const rawEmail = profile?.emails?.[0]?.value;\n                    if (!rawEmail) {\n                        winston.warn(\"Google profile has no email\", {issuer: issuer, profileId: profile?.id});\n                        return cb(null, false, {message: \"Missing email from Google profile.\"});\n                    }\n\n                    const email = rawEmail.toLowerCase().trim();\n                    winston.debug(\"email: \" + email);\n\n                    const query = {providerId: issuer, subject: profile.id};\n                    winston.debug(\"query\", query);\n\n                    const cred = await Auth.findOne(query).exec();\n                    winston.debug(\"cred\", cred);\n\n                    if (cred) {\n                        // Already linked: return the corresponding user\n                        const user = await User.findOne({email: email, status: 100})\n                            .select('email firstname lastname password emailverified id')\n                            .exec();\n\n                        if (!user) {\n                            winston.warn(\"Auth link exists but user not found/active\", {email: email, query: query});\n                            return cb(null, false, {message: \"User not found.\"});\n                        }\n\n                        return cb(null, user);\n                    }\n\n                    // Not linked yet: try to reuse existing account by email\n                    let user = await User.findOne({email: email, status: 100})\n                        .select('email firstname lastname password emailverified id')\n                        .exec();\n\n                    if (!user) {\n                        // No existing user -> create one\n                        const password = uniqid();\n                        try {\n                            user = await userService.signup(email, password, profile.displayName, \"\", true);\n                        } catch (err) {\n                            // Race/duplicate: fall back to existing user\n                            if (err && (err.code === 11000 || err.code === \"E11000\")) {\n                                user = await User.findOne({email: email, status: 100}).exec();\n                            } else {\n                                winston.error(\"Error signup google\", err);\n                                return cb(err);\n                            }\n                        }\n                    }\n\n                    if (!user) {\n                        return cb(null, false, {message: \"User not found.\"});\n                    }\n\n                    // Ensure Auth link is created (idempotent)\n                    await Auth.findOneAndUpdate(\n                        query,\n                        {$setOnInsert: {providerId: issuer, email: email, subject: profile.id}},\n                        {upsert: true, new: true}\n                    ).exec();\n\n                    return cb(null, user);\n                } catch (err) {\n                    winston.error(\"Google strategy verify error\", err);\n                    return cb(err);\n                }\n            }\n        ));\n\n    }\n\n\n    if (enableOauth2Signin == true) {\n\n        const OAuth2Strategy = require('passport-oauth2');\n        OAuth2Strategy.prototype.userProfile = function (accessToken, done) {\n\n            winston.debug(\"accessToken \" + accessToken)\n\n\n            /*\n            https://stackoverflow.com/questions/66452108/keycloak-get-users-returns-403-forbidden\n          The service account associated with your client needs to be allowed to view the realm users.\n          Go to http://localhost:8080/auth/admin/{realm_name}/console/#/realms/{realm_name}/clients\n          Select your client (which must be a confidential client)\n          In the settings tab, switch Service Account Enabled to ON\n          Click on save, the Service Account Roles tab will appear\n          In Client Roles, select realm_management\n          Scroll through available roles until you can select view_users\n          Click on Add selected\n          You should have something like this :\n          */\n\n\n            // ATTENTION You have to add a client scope after as described here: https://keycloak.discourse.group/t/issue-on-userinfo-endpoint-at-keycloak-20/18461/4\n\n            // console.log(\"this._oauth2\", this._oauth2)\n            this._oauth2._useAuthorizationHeaderForGET = true;\n            this._oauth2.get(process.env.OAUTH2_USER_INFO_URL, accessToken, (err, body) => {\n                if (err) {\n                    return done(err);\n                }\n\n                try {\n                    winston.debug(\"body\", body);\n\n                    const json = JSON.parse(body);\n                    const userInfo = {\n                        keycloakId: json.sub,\n                        fullName: json.name,\n                        firstName: json.given_name,\n                        lastName: json.family_name,\n                        username: json.preferred_username,\n                        email: json.email,\n                        // avatar: json.avatar,\n                        // realm: this.options.realm,\n                    };\n                    winston.debug(\"userInfo\", userInfo);\n\n                    done(null, userInfo);\n                } catch (e) {\n                    done(e);\n                }\n            });\n        };\n\n\n        passport.use(new OAuth2Strategy({\n                authorizationURL: process.env.OAUTH2_AUTH_URL,\n                tokenURL: process.env.OAUTH2_TOKEN_URL,\n                clientID: process.env.OAUTH2_CLIENT_ID,\n                clientSecret: process.env.OAUTH2_CLIENT_SECRET,\n                callbackURL: process.env.OAUTH2_CALLBACK_URL || \"http://localhost:3000/auth/oauth2/callback\",\n                scope: ['openid'],\n            },\n            function (accessToken, refreshToken, params, profile, cb) {\n                winston.debug(\"params\", params);\n\n\n                const token = jwt.decode(accessToken); // user id lives in here\n                winston.debug(\"token\", token);\n\n                const profileInfo = jwt.decode(params.access_token); // user email lives in here\n                winston.debug(\"profileInfo\", profileInfo);\n\n                winston.debug(\"profile\", profile);\n\n                winston.debug(\"accessToken\", accessToken);\n\n                winston.debug(\"refreshToken\", refreshToken);\n\n                var issuer = token.iss;\n                var email = profile.email;\n\n                var query = {providerId: issuer, subject: profile.keycloakId};\n                winston.debug(\"query\", query)\n\n                Auth.findOne(query, function (err, cred) {\n                    winston.debug(\"cred\", cred, err);\n                    if (err) {\n                        return cb(err);\n                    }\n                    if (!cred) {\n                        // The oauth account has not logged in to this app before.  Create a\n                        // new user record and link it to the oauth account.\n                        var password = uniqid()\n                        // signup ( email, password, firstname, lastname, emailverified) {\n                        userService.signup(email, password, profileInfo.name || profileInfo.preferred_username, \"\", true)\n                            .then(function (savedUser) {\n\n                                winston.debug(\"savedUser\", savedUser)\n\n                                var auth = new Auth({\n                                    providerId: issuer,\n                                    email: email,\n                                    subject: profile.keycloakId,\n                                });\n                                auth.save(function (err, authSaved) {\n                                    if (err) {\n                                        return cb(err);\n                                    }\n                                    winston.debug(\"authSaved\", authSaved);\n\n                                    return cb(null, savedUser);\n                                });\n                            }).catch(function (err) {\n                            winston.error(\"Error signup oauth \", err);\n                            return cb(err);\n                        });\n                    } else {\n                        // The Oauth account has previously logged in to the app.  Get the\n                        // user record linked to the Oauth account and log the user in.\n\n                        User.findOne({\n                            email: email, status: 100\n                        }, 'email firstname lastname emailverified id', function (err, user) {\n\n                            winston.debug(\"user\", user, err);\n                            // winston.debug(\"usertoJSON()\",user.toJSON());\n\n                            if (err) {\n                                winston.error(\"Error getting user\", user, err);\n                                return cb(err);\n                            }\n\n                            if (!user) {\n                                winston.info(\"User not found\", user, err);\n                                return cb(null, false);\n                            }\n\n                            return cb(null, user);\n                        });\n                    }\n                });\n            }\n        ));\n    }\n\n\n// const KeycloakStrategy = require('@exlinc/keycloak-passport')\n\n\n// // Register the strategy with passport\n// passport.use(\n//   \"keycloak\",\n//   new KeycloakStrategy(\n//     {\n//       host: process.env.KEYCLOAK_HOST,\n//       realm: process.env.KEYCLOAK_REALM,\n//       clientID: process.env.KEYCLOAK_CLIENT_ID,\n//       clientSecret: process.env.KEYCLOAK_CLIENT_SECRET,\n//       callbackURL: `${process.env.AUTH_KEYCLOAK_CALLBACK}`,\n//       authorizationURL : `${process.env.KEYCLOAK_HOST}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/auth`,\n//       tokenURL : `${process.env.KEYCLOAK_HOST}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/token`,\n//       userInfoURL : `${process.env.KEYCLOAK_HOST}/realms/${process.env.KEYCLOAK_REALM}/protocol/openid-connect/userinfo`\n//       // authorizationURL: '123',\n//       // tokenURL : '123',\n//       // userInfoURL: '123'\n//     },\n//     (accessToken, refreshToken, profile, done) => {\n\n\n//       const token = jwt.decode(accessToken); // user id lives in here\n//       console.log(\"token\", token);\n\n//       console.log(\"profile\", profile);\n\n//       console.log(\"accessToken\", accessToken);\n\n//       console.log(\"refreshToken\", refreshToken);\n\n//       var issuer = token.iss;\n//       var email = profile.email;\n\n//       var query = {providerId : issuer, subject: profile.keycloakId};\n//       winston.info(\"query\", query)\n\n//       Auth.findOne(query, function(err, cred){     \n//       winston.info(\"cred\", cred, err);\n//         if (err) { return cb(err); }\n//         if (!cred) {\n//           // The oauth account has not logged in to this app before.  Create a\n//           // new user record and link it to the oauth account.\n//             var password = uniqid()\n//            // signup ( email, password, firstname, lastname, emailverified) {\n//             userService.signup(email, password,  profile.displayName, \"\", true)\n//             .then(function (savedUser) {\n\n//             winston.info(\"savedUser\", savedUser)    \n\n//             var auth = new Auth({\n//               providerId: issuer,\n//               email: email,\n//               subject: profile.keycloakId,\n//             });\n//             auth.save(function (err, authSaved) {    \n//               if (err) { return cb(err); }\n//               winston.info(\"authSaved\", authSaved);\n\n//               return cb(null, savedUser);\n//             });\n//           }).catch(function(err) {\n//               winston.error(\"Error signup oauth \", err);\n//               return cb(err);        \n//           });\n//         } else {\n//           // The Oauth account has previously logged in to the app.  Get the\n//           // user record linked to the Oauth account and log the user in.\n\n//           User.findOne({\n//             email: email, status: 100\n//           }, 'email firstname lastname emailverified id', function (err, user) {\n\n//             winston.info(\"user\",user, err);\n//             winston.info(\"usertoJSON()\",user.toJSON());\n\n//             if (err) { \n//               winston.error(\"Error getting user\",user, err);\n//               return cb(err); \n//             }\n\n//             if (!user) { \n//               winston.info(\"User not found\",user, err);\n//               return cb(null, false); \n//             }\n\n//             return done(null, user);\n//           });\n//         }\n//       });\n//     }\n//     ));\n\n\n    // var OidcStrategy = require('passport-openidconnect').Strategy;\n\n\n    // https://github.com/jaredhanson/passport-anonymous\n\n    // passport.use(new AnonymousStrategy());\n\n\n// link utili\n// https://codeburst.io/how-to-implement-openid-authentication-with-openid-client-and-passport-in-node-js-43d020121e87?gi=4bb439e255a7\n    // https://developer.wordpress.com/docs/oauth2/\n\n\n    // openidconnect\n    // https://docs.simplelogin.io/docs/passport/\n\n\n    // oauth2\n    /**\n     * BasicStrategy & ClientPasswordStrategy\n     *\n     * These strategies are used to authenticate registered OAuth clients. They are\n     * employed to protect the `token` endpoint, which consumers use to obtain\n     * access tokens. The OAuth 2.0 specification suggests that clients use the\n     * HTTP Basic scheme to authenticate. Use of the client password strategy\n     * allows clients to send the same credentials in the request body (as opposed\n     * to the `Authorization` header). While this approach is not recommended by\n     * the specification, in practice it is quite common.\n     */\n\n    /*\n  const ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy;\n  \n  function verifyClient(clientId, clientSecret, done) {\n    \n    db.clients.findByClientId(clientId, (error, client) => {\n      if (error) return done(error);\n      if (!client) return done(null, false);\n      if (client.clientSecret !== clientSecret) return done(null, false);\n      return done(null, client);\n    });\n  }\n  \n  //passport.use(new BasicStrategy(verifyClient));\n  \n  passport.use(new ClientPasswordStrategy(verifyClient));\n  \n  \n  */\n\n\n};\n"
  },
  {
    "path": "middleware/recaptcha.js",
    "content": "var winston = require('../config/winston');\n\nvar Recaptcha = require('express-recaptcha').RecaptchaV3;\n\nconst recaptcha_key = process.env.RECAPTCHA_KEY;\nconst recaptcha_secret = process.env.RECAPTCHA_SECRET;\n\nwinston.info(\"Recaptcha key: \" + recaptcha_key + \" and secret: \" + recaptcha_secret );\nlet recaptcha;\n\nlet RECAPTCHA_ENABLED =  false;\n\nif (process.env.RECAPTCHA_ENABLED === true || process.env.RECAPTCHA_ENABLED ===\"true\") {\n    recaptcha = new Recaptcha(recaptcha_key, recaptcha_secret);\n    RECAPTCHA_ENABLED = true;\n}\n\nmodule.exports = \n    function(req,res,next){ \n        if (RECAPTCHA_ENABLED==false) {\n            return next();\n        } \n\n        recaptcha.verify(req, function (error, data) {\n            if (!error) {\n              winston.debug(\"Signup recaptcha ok\");\n              next();\n                // success code\n            } else {\n              winston.error(\"Signup recaptcha ko: \"+ error);\n            //   next({status:\"Signup recaptcha ko\"});\n                res.status(403).send({success: false, msg: 'Recaptcha error.'});\n\n                // error code\n            }\n        })\n       \n}\n"
  },
  {
    "path": "middleware/valid-token.js",
    "content": "\nmodule.exports = function(req, res, next) {\n        // winston.debug(\"valid-token\");\n        var token = getToken(req.headers);\n        // winston.debug(\"token\", token);\n        if (token) {\n           next();\n        } else {\n          return res.status(403).send({success: false, msg: 'Unauthorized.'});\n        }\n    }\n  \n\n\n\n\ngetToken = function (headers) {\n    if (headers && headers.authorization) {\n      var parted = headers.authorization.split(' ');\n      if (parted.length === 2) {\n        return parted[1];\n      } else {\n        return null;\n      }\n    } else {\n      return null;\n    }\n  };"
  },
  {
    "path": "migrations/1601628781595-project_users_presence.js",
    "content": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () {\n  // Write migration here\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Project_user.updateMany({}, {\"$set\": {presence: {status: \"offline\", changedAt: new Date()}}}, function (err, updates) {\n      winston.info(\"Schema updated for \" + updates.nModified + \" project_user\")\n       return resolve('ok'); \n    });  \n  });\n     \n \n} \n  \n/** \n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n  // console.log(\"down*********\");\n}\n\nmodule.exports = { up, down }; \n  "
  },
  {
    "path": "migrations/1602847963299-message-channel_type-and-channel-fields-added--autosync.js",
    "content": "var Message = require(\"../models/message\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Message.updateMany({}, {\"$set\": {channel: {name: \"chat21\"}, channel_type: \"group\"}}, function (err, updates) {\n        winston.info(\"Schema updated for \" + updates.nModified + \" messages\")\n       return resolve('ok'); \n    });  \n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1603797978971-project_users-status-field-added--autosync.js",
    "content": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () {\n  // Write migration here\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Project_user.updateMany({}, {\"$set\": {status: \"active\"}}, function (err, updates) {\n      winston.info(\"Schema updated for \" + updates.nModified + \" project_user status active field\")\n       return resolve('ok'); \n    });  \n  });\n     \n \n} \n  \n/** \n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n  // console.log(\"down*********\");\n}\n\nmodule.exports = { up, down }; \n  "
  },
  {
    "path": "migrations/1603955232377-requests-channel-outbound-fields--autosync.js",
    "content": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Request.updateMany({}, {\"$set\": {channelOutbound: {name: \"chat21\"}}}, function (err, updates) {\n        winston.info(\"Schema updated for \" + updates.nModified + \" requests channelOutbound\")\n       return resolve('ok'); \n    });  \n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1603955232378-requests-channel-fields--autosync.js",
    "content": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Request.updateMany({}, {\"$set\": {channel: {name: \"chat21\"}}}, function (err, updates) {\n        winston.info(\"Schema updated for \" + updates.nModified + \" requests channel\")\n       return resolve('ok'); \n    });  \n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1604082287722-labels-data-default-fields--autosync.js",
    "content": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n   \n    // { $exists: true } to fix: The positional operator did not find the match needed from the query.\n    Label.updateMany({ data:  { $exists: true }, \"data.1\": { $exists: false } }, {\"$set\": {\"data.$.default\": true}}, function (err, updates) {\n      if (err) { \n        winston.error(\"Schema migration: label err1\", err);\n      }\n      winston.info(\"Schema updated for \" + updates.nModified + \" label with single data to default field\")\n          return resolve('ok');  \n    });  \n    // {\"data\": { $elemMatch: {\"lang\": {  $ne: \"EN\" }}}}  \n    // Label.updateMany({$where: \"this.data.length > 1\", 'data.lang': {$ne: \"EN\"}} , {\"$set\": {\"data.$[].default\": false}}, function (err, updates) {\n    // Label.updateMany({$where: \"this.data.length > 1\", 'data.lang': {$nin: [\"EN\"]}} , {\"$set\": {\"data.$[].default\": false}}, function (err, updates) {\n    Label.updateMany({ data: { $elemMatch: { \"lang\": {  $ne: \"EN\" } } }, \"data.1\": { $exists: true } } , {\"$set\": {\"data.$[].default\": false}}, function (err, updates) {\n      if (err) {\n        winston.error(\"Schema migration: label err2\", err);\n      }\n    winston.info(\"Schema updated for \" + updates.nModified + \" label to default false field\")\n\n    Label.updateMany({\"data.lang\": \"EN\", \"data.1\": { $exists: true } }, {\"$set\": {\"data.$.default\": true}}, function (err, updates) {\n      if (err) {\n        winston.error(\"Schema migration: label err3\", err);\n      }\n      winston.info(\"Schema updated for \" + updates.nModified + \" label with multiple data to default field\")\n        return resolve('ok'); \n    });  \n\n          // return resolve('ok');    \n    });  \n\n\n    // db.getCollection('labels').find({}).count() 465\n    // db.getCollection('labels').find({\"$where\": \"this.data.length > 1\", \"data.lang\": \"EN\"}).count() 92\n    // db.getCollection('labels').find({\"$where\": \"this.data.length > 1\"}).count() 92\n    \n\n\n\n    // Lang not in english \n    // db.getCollection('labels').find({'data.lang': {$nin: [\"EN\"]}})\n\n    // default not exists\n    // db.getCollection('labels').find({'data.default': { $exists: false }}) \n\n       \n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1604082288723-labels-waiting_time-added_suffix_reply_time--autosync.js",
    "content": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n                  \n    Label.find( \n      {\n        // \"data.data.WAITING_TIME_FOUND\":  { $exists: true } \n      }, function(err, labels) {\n      if (err) {\n        winston.error(\"Schema migration: label WAITING_TIME_FOUND err\", err);\n        return reject(err);\n      }\n      labels.forEach(\n        function(label, i){ \n          // console.log(label,i); \n          label.data.forEach(function(data, i) { \n            // console.log(data,i); \n            data.data.WAITING_TIME_FOUND = data.data.WAITING_TIME_FOUND + \" $reply_time\";\n          });\n          // console.log(\"after\",JSON.stringify(label),i); \n          Label.findOneAndUpdate({_id: label.id}, label, {upsert: true}, function(err, doc) {\n            if (err) {\n              winston.error(\"Schema migration: label WAITING_TIME_FOUND update err\", err);\n            } else {\n              //ok\n            }\n          });\n          // label.markModified('data');\n          // // label.save();  \n          // label.update();\n          // e.name='Big ';\n          // Label.collection.save(e);\n        }\n     )\n\n     winston.info(\"Schema updated label WAITING_TIME_FOUND  update\")\n\n    \n     return resolve(\"ok\"); \n    });\n  \n\n    // Label.updateMany({\"data.data.WAITING_TIME_FOUND\":  { $exists: true } }, [{ $set: { \"data.data.WAITING_TIME_FOUND\": { $concat: [ \"$data.data.$WAITING_TIME_FOUND\", \" $reply_time\" ] } } }], function (err, updates) {\n    //   // Label.updateMany({\"data.data.WAITING_TIME_FOUND\":  { $exists: true } }, [{ $set: { \"data.$[].data.WAITING_TIME_FOUND\": { $concat: [ \"$wt\", \" $reply_time\" ] } } }], function (err, updates) {\n    //   if (err) {  \n    //     winston.error(\"Schema migration: label WAITING_TIME_FOUND err\", err);\n    //   } \n    //   winston.info(\"Schema updated for \" + updates.nModified + \" label WAITING_TIME_FOUND\")\n    //       return resolve('ok');  \n    // });  \n    \n  \n       \n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n  "
  },
  {
    "path": "migrations/1611576399823-project-settings-max_agent_assigned_renamed--autosync.js",
    "content": "var Project = require(\"../models/project\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () {\n  // Write migration here\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Project.updateMany({\"settings.max_agent_served_chat\":{$exists:true}}, { $rename: { \"settings.max_agent_served_chat\": \"settings.max_agent_assigned_chat\" } } , function (err, updates) {\n      if (err) {\n        winston.error(\"Schema updated  project.settings max_assigned_chat error \",err);\n        return reject(err);\n      }\n      winston.info(\"Schema updated for \" + updates.nModified + \" project.settings max_assigned_chat field\")\n       return resolve('ok'); \n    });  \n  }); \n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1611576534533-project_user-max_assigned_chat-renamed--autosync.js",
    "content": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () {\n  // Write migration here\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n\n    //https://github.com/Automattic/mongoose/issues/3171\n    return Project_user.collection.updateMany({\"max_served_chat\":{$exists:true}}, { $rename: { \"max_served_chat\": \"max_assigned_chat\" } } , function (err, updates) {\n      if (err) {\n        winston.error(\"Schema updated project_user max_assigned_chat  error \",err);\n        return reject(err);\n      }\n      winston.info(\"Schema updated for \" + updates.nModified + \" project_user max_assigned_chat field\")\n       return resolve('ok'); \n    });  \n  });\n}\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n}\n\nmodule.exports = { up, down };\n"
  },
  {
    "path": "migrations/1615214914082-faq-intent_id-intent_display_name-fields-added--autosync.js",
    "content": "var Faq = require(\"../models/faq\");\nvar winston = require('../config/winston');\nvar { nanoid } = require(\"nanoid\");\nconst uuidv4 = require('uuid/v4');\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () {\n   \n  await new Promise(async (resolve, reject) => {\n  // Write migration here\n   var faqs = await Faq.find({});\n    faqs.forEach(\n       function(faq, i){ \n        Faq.findOneAndUpdate({_id: faq.id}, {\"$set\": {intent_id: uuidv4(), intent_display_name: nanoid(6)}}, function(err, doc) {\n        // updateMany({}, {\"$set\": {intent_id: \"UUID()\", intent_display_name: \"UUID()\"  }} , function (err, updates) {\n        if (err) {\n          winston.error(\"Schema updated  faq intent_id intent_display_name \",err);\n          return reject(err);\n        }                \n        });   \n      });\n      winston.info(\"Schema updated for \" + faqs.length + \" faq intent_id intent_display_name field\")\n      return resolve('ok'); \n    });\n  \n\n}   \n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1616685902635-request_agents_to_snapshot_agents--autosync.js",
    "content": "var Request = require(\"../models/request\");\nvar winston = require('../config/winston');\nconst request = require(\"../models/request\");\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // setTimeout(()=> { resolve('ok'); }, 3000);\n    return Request.find(\n      {\n        \"snapshot.agents\":  { $exists: false } ,\n        \"agents\":  { $exists: true } ,\n      })\n      .lean()\n      // .limit(20000)\n      .exec(function(err, requests) {\n    if (err) {\n      winston.error(\"Schema migration: agents err\", err);\n      return reject(err);\n    }\n    requests.forEach( \n      function(request, i){ \n        winston.debug(request.agents,i);\n        // console.log(request,i); \n\n        // request.snapshot.agents = request.agents;\n\n        Request.findOneAndUpdate({_id: request._id}, {\"$set\": {\"snapshot.agents\":request.agents}}, {upsert: true}, function(err, doc) {\n          if (err) {\n            winston.error(\"Schema migration: label agents update err\", err);              \n          } \n          // winston.debug(\"ok\",doc);\n          // return resolve('ok');\n        }); \n\n\n        // request.snapshot.agents = request.agents;\n        // request.markModified('snapshot');\n        // request.save(function(err, data) {\n        //   console.log(\"Error\",err);\n        // });\n      });\n\n      winston.info(\"Updated request agents found: \" + requests.length);\n      return resolve('ok');\n    });\n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n "
  },
  {
    "path": "migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents--autosync.js",
    "content": "// var Trigger = require(\"../models/request\");\nvar mongoose = require('mongoose');\n\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // winston.info(\"XXXXXXXXXXXXXXXXXXXXXXXXXX triggers \");\n \n    \n    // db.getCollection('triggers').find({\"conditions.all.path\":\"availableAgentsCount\"})\n    // return mongoose.connection.db.collection('triggers').updateOne({\"conditions.all.path\":\"availableAgentsCount\"},{ \"$set\": { \"conditions.all.$.path\": \"snapshot.availableAgentsCount\" } }, function (err, updates) {\n      return mongoose.connection.db.collection('triggers').find(\n        {\"$or\": [ \n          {\"conditions.all.path\":\"snapshot.availableAgentsCount\"},\n          {\"conditions.all.path\":\"availableAgentsCount\"}\n          ]\n        }\n        ).toArray( function (err, triggers) {\n        if (err) {\n          winston.error(\"Schema migration: triggers err\", err);\n        }\n        winston.debug(\"found triggers\", triggers);\n        winston.debug(\"found triggers\"+triggers.length);\n\n        triggers.forEach( \n          function(trigger, i){ \n            winston.debug(\"trigger\",trigger);\n            return mongoose.connection.db.collection('triggers').updateOne({_id:trigger._id, \"conditions.all.path\":\"availableAgentsCount\"},{ \"$set\": { \"conditions.all.$.path\": \"snapshot.availableAgentsCount\",\"conditions.all.$.key\": \"request.snapshot.availableAgentsCount\" } }\n            // {\n            //   multi: true,\n            //   arrayFilters: [ {\"conditions.all.path\":\"availableAgentsCount\"} ] \n            // }\n            ,\n             function (err, updates) {\n            // return mongoose.connection.db.collection('triggers').updateMany({\"conditions.all.path\":\"availableAgentsCount\"},{ \"$set\": { \"conditions.all.$[0].path\": \"snapshot.availableAgentsCount\" } }, function (err, updates) {\n            if (err) {\n              winston.error(\"Schema migration: triggers err\", err);  \n            }\n            // winston.info(\"Schema updated for \" + updates.n Modified + \" triggers \");\n        });\n      }); \n      winston.info(\"Schema updated path for triggers \"+triggers.length);\n       return resolve('ok'); \n    });  \n    \n  }); \n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible )\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n \n\n// db.getCollection('triggers').find({\"conditions.all.key\":\"request.availableAgentsCount\"}).count() -> 479\n\n// db.getCollection('triggers').find({\"conditions.all.key\":\"request.snapshot.availableAgentsCount\"}) -> 4\n\n// db.getCollection('triggers').find({\"conditions.all.path\":\"snapshot.availableAgentsCount\"}).count() -> 481"
  },
  {
    "path": "migrations/1616687831941-trigger_availableAgentsCount_to_snapshot_agents-key--autosync.js",
    "content": "// var Trigger = require(\"../models/request\");\nvar mongoose = require('mongoose');\n\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n * ./node_modules/.bin/migrate create message-channel_type-and-channel-fields-added--autosync\n */\nasync function up () {\n  await new Promise((resolve, reject) => {\n    // winston.info(\"XXXXXXXXXXXXXXXXXXXXXXXXXX triggers \");\n \n    \n    // db.getCollection('triggers').find({\"conditions.all.path\":\"availableAgentsCount\"})\n    // return mongoose.connection.db.collection('triggers').updateOne({\"conditions.all.path\":\"availableAgentsCount\"},{ \"$set\": { \"conditions.all.$.path\": \"snapshot.availableAgentsCount\" } }, function (err, updates) {\n      return mongoose.connection.db.collection('triggers').find(\n        // {\n          // \"$or\": [ \n          {\"conditions.all.key\":\"request.availableAgentsCount\"},\n          // {\"conditions.all.key\":\"availableAgentsCount\"}\n          // ]\n        // }\n        ).toArray( function (err, triggers) {\n        if (err) {\n          winston.error(\"Schema migration: triggers err\", err);\n        }\n        winston.debug(\"found triggers\", triggers);\n        winston.debug(\"found triggers\"+triggers.length);\n\n        triggers.forEach( \n          function(trigger, i){ \n            winston.debug(\"trigger\",trigger);\n            return mongoose.connection.db.collection('triggers').updateOne({_id:trigger._id, \"conditions.all.key\":\"request.availableAgentsCount\"},{ \"$set\": {\"conditions.all.$.key\": \"request.snapshot.availableAgentsCount\" } }\n            // {\n            //   multi: true,\n            //   arrayFilters: [ {\"conditions.all.path\":\"availableAgentsCount\"} ] \n            // }\n            ,\n             function (err, updates) {\n            // return mongoose.connection.db.collection('triggers').updateMany({\"conditions.all.path\":\"availableAgentsCount\"},{ \"$set\": { \"conditions.all.$[0].path\": \"snapshot.availableAgentsCount\" } }, function (err, updates) {\n            if (err) {\n              winston.error(\"Schema migration: triggers err\", err);  \n            }\n            // winston.info(\"Schema updated for \" + updates.n Modified + \" triggers \");\n        });\n      }); \n      winston.info(\"Schema updated key for triggers \"+triggers.length);\n       return resolve('ok'); \n    });  \n    \n  }); \n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible )\n */\nasync function down () { \n  // Write migration here\n}\n\nmodule.exports = { up, down };\n \n\n// db.getCollection('triggers').find({\"conditions.all.key\":\"request.availableAgentsCount\"}).count() -> 479\n\n// db.getCollection('triggers').find({\"conditions.all.key\":\"request.snapshot.availableAgentsCount\"}) -> 4\n\n// db.getCollection('triggers').find({\"conditions.all.path\":\"snapshot.availableAgentsCount\"}).count() -> 481"
  },
  {
    "path": "migrations/1619185894304-request-remove-duplicated-request-by-request_id--autosync.js",
    "content": "var mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up () { \n  // Write migration here \n  await new Promise((resolve, reject) => {\n\n  winston.info(\"Duplicate\");\n\n\n  return mongoose.connection.db.collection('requests').aggregate(\n    {\"$group\" : { \"_id\": \"$request_id\", \"count\": { \"$sum\": 1 } } },\n    {\"$match\": {\"_id\" :{ \"$ne\" : null }, \"count\" : {\"$gt\": 1} } }, //\"duplicated_request_id\":{\"$ne\":true} \n    {\"$sort\": {\"count\" : -1} },\n    {\"$project\": {\"request_id\" : \"$_id\", \"_id\" : 0} }\n  ).toArray( function (err, requests) {\n    if (err) {\n      winston.error(\"Schema migration: requests err\", err);\n    }\n\n    winston.info(\"found requests\", requests);\n    winston.info(\"found requests.length\"+requests.length);\n \n    \n    requests.forEach(   \n      function(request, i){ \n\n        winston.info(\"request\",request); \n        winston.info(\"request._id: \" + request._id); \n        \n\n        return mongoose.connection.db.collection('requests')\n          //.findOne({request_id:request._id},\n          // ,\"id_project\": request.id_project+\"_deleted\", \n          .updateOne({request_id:request._id},{ \"$set\": { \"request_id\": request._id+\"_duplicated\", \"duplicated_request_id\":true } },\n          function (err, updates) {          \n            if (err) {\n              winston.error(\"Schema migration: requests err\", err);  \n            } \n            winston.info(\"updates\",updates); \n          });\n         \n      });\n      \n\n\n      winston.info(\"Schema updated path for requests \"+requests.length);\n      return resolve('ok'); \n\n    }); \n\n  });\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down () {\n  // Write migration here\n}\n\nmodule.exports = { up, down };\n\n\n"
  },
  {
    "path": "migrations/1752742733903-namespace-engine-migration.js",
    "content": "var winston = require('../config/winston');\nconst { Namespace } = require('../models/kb_setting');\n\nasync function up () {\n\n  if (!process.env.PINECONE_INDEX || !process.env.PINECONE_TYPE) {\n    winston.error(\"Namespace engine migration STOPPED. PINECONE_TYPE or PINECONE_INDEX undefined.\");\n    return;\n  }\n\n  let engine = {\n    name: \"pinecone\",\n    type: process.env.PINECONE_TYPE,\n    apikey: \"\",\n    vector_size: 1536,\n    index_name: process.env.PINECONE_INDEX\n  };\n\n  try {\n    const updates = await Namespace.updateMany(\n      { engine: { $exists: false } },\n      { engine: engine }\n    );\n    winston.info(\"Schema updated for \" + updates.nModified + \" namespace\");\n  } catch (err) {\n    winston.error(\"Error updating namespaces in migration:\", err);\n    throw err;\n  }\n}\n\nmodule.exports = { up }; "
  },
  {
    "path": "migrations/1757601159298-project_user_role_type.js",
    "content": "var Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\nconst BATCH_SIZE = 100;\n/** Log a progress line every this many bulkWrite rounds (plus always after the 1st). */\nconst PROGRESS_LOG_EVERY_BATCHES = 10;\n\n/** Only documents that still need roleType (idempotent re-runs). */\nconst WITHOUT_ROLE_TYPE = {\n  $or: [{ roleType: { $exists: false } }, { roleType: null }]\n};\n\nconst AGENT_ROLES_FILTER = {\n  $and: [\n    {\n      $or: [\n        { role: 'agent' },\n        { role: 'supervisor' },\n        { role: 'admin' },\n        { role: 'owner' }\n      ]\n    },\n    WITHOUT_ROLE_TYPE\n  ]\n};\n\nconst USER_ROLES_FILTER = {\n  $and: [{ $or: [{ role: 'user' }, { role: 'guest' }] }, WITHOUT_ROLE_TYPE]\n};\n\nasync function batchSetRoleType(filter, roleType, phaseLabel) {\n  const totalMatching = await Project_user.countDocuments(filter);\n  const started = Date.now();\n  winston.info(\n    `[project_user_role_type] ${phaseLabel}: ${totalMatching} documents match filter; streaming _id + bulkWrite in chunks of ${BATCH_SIZE}`\n  );\n\n  let modified = 0;\n  let scanned = 0;\n  let bulkRounds = 0;\n  const cursor = Project_user.find(filter).select('_id').lean().cursor();\n  let batch = [];\n\n  function logProgress(reason) {\n    const elapsedSec = ((Date.now() - started) / 1000).toFixed(1);\n    winston.info(\n      `[project_user_role_type] ${phaseLabel} ${reason}: scanned=${scanned}/${totalMatching}, modifiedThisPhase=${modified}, ${elapsedSec}s elapsed`\n    );\n  }\n\n  for await (const doc of cursor) {\n    scanned++;\n    batch.push({\n      updateOne: {\n        filter: { _id: doc._id, ...WITHOUT_ROLE_TYPE },\n        update: { $set: { roleType } }\n      }\n    });\n    if (batch.length >= BATCH_SIZE) {\n      const result = await Project_user.bulkWrite(batch);\n      modified += result.modifiedCount;\n      batch = [];\n      bulkRounds++;\n      if (bulkRounds === 1 || bulkRounds % PROGRESS_LOG_EVERY_BATCHES === 0) {\n        logProgress(`progress (bulk round ${bulkRounds})`);\n      }\n    }\n  }\n  if (batch.length > 0) {\n    const result = await Project_user.bulkWrite(batch);\n    modified += result.modifiedCount;\n    bulkRounds++;\n    logProgress(`final partial batch (bulk round ${bulkRounds})`);\n  }\n\n  logProgress('phase complete');\n  return modified;\n}\n\n/**\n * Make any changes you need to make to the database here\n */\nasync function up() {\n  try {\n    const agentsModified = await batchSetRoleType(AGENT_ROLES_FILTER, 1, 'agents roleType=1');\n    winston.info(`[project_user_role_type] Agents phase done: ${agentsModified} documents modified`);\n\n    const usersModified = await batchSetRoleType(USER_ROLES_FILTER, 2, 'users/guest roleType=2');\n    winston.info(`[project_user_role_type] Users/guest phase done: ${usersModified} documents modified`);\n  } catch (err) {\n    winston.error('[project_user_role_type] Error applying migration:', err);\n    throw err;\n  }\n}\n\n/**\n * Make any changes that UNDO the up function side effects here (if possible)\n */\nasync function down() {\n  // Write migration here\n  // console.log(\"down*********\");\n}\n\nmodule.exports = { up, down };\n"
  },
  {
    "path": "migrations/1771844588961-phone-channels-migration.js",
    "content": "var winston = require('../config/winston');\nconst Request = require('../models/request');\nconst phoneUtil = require('../utils/phoneUtil');\n\nconst VOICE_TWILIO_CHANNEL_NAMES = ['voice_twilio', 'voice-twilio'];\nconst VOICE_VXML_CHANNEL_NAMES = ['voice-vxml', 'voice-vxml-enghouse'];\nconst BATCH_SIZE = 100;\n\nfunction getPhoneFromVoiceTwilioCreatedBy(createdBy) {\n  if (!createdBy || typeof createdBy !== 'string') return null;\n  if (createdBy.startsWith('voice-twilio-')) return createdBy.replace(/^voice-twilio-/, '');\n  if (createdBy.startsWith('voice_twilio-')) return createdBy.replace(/^voice_twilio-/, '');\n  return null;\n}\n\nasync function updateManyWithNormalizedPhone(filter, getPhoneFromDoc) {\n  let matched = 0;\n  let modified = 0;\n  const cursor = Request.find(filter).select('_id attributes createdBy').lean().cursor();\n  let batch = [];\n  for await (const doc of cursor) {\n    const rawPhone = getPhoneFromDoc(doc);\n    if (rawPhone == null || String(rawPhone).trim() === '') continue;\n    matched++;\n    const normalized = phoneUtil.normalizePhone(rawPhone);\n    const value = normalized != null && normalized !== '' ? normalized : String(rawPhone).trim();\n    if (value === '') continue;\n    batch.push({\n      updateOne: {\n        filter: { _id: doc._id },\n        update: { $set: { 'contact.phone': value } }\n      }\n    });\n    if (batch.length >= BATCH_SIZE) {\n      const result = await Request.bulkWrite(batch);\n      modified += result.modifiedCount;\n      batch = [];\n    }\n  }\n  if (batch.length > 0) {\n    const result = await Request.bulkWrite(batch);\n    modified += result.modifiedCount;\n  }\n  return { matched, modified };\n}\n\nasync function up() {\n  try {\n    // Voice Twilio (voice-twilio / voice_twilio: phone from createdBy as \"voice-twilio-{phone}\" or \"voice_twilio-{phone}\")\n    const voiceFilter = {\n      'channel.name': { $in: VOICE_TWILIO_CHANNEL_NAMES },\n      createdBy: { $regex: /^(voice-twilio-|voice_twilio-)/ }\n    };\n    winston.info(`[phone-channels-migration] Voice Twilio Starting...`);\n    const voiceResult = await updateManyWithNormalizedPhone(voiceFilter, (doc) =>\n      getPhoneFromVoiceTwilioCreatedBy(doc.createdBy)\n    );\n    winston.info(`[phone-channels-migration] Voice Twilio: matched ${voiceResult.matched}, modified ${voiceResult.modified}`);\n\n    // Voice VXML (voice-vxml / voice-vxml-enghouse: phone from attributes.caller_phone)\n    const voiceVxmlFilter = {\n      'channel.name': { $in: VOICE_VXML_CHANNEL_NAMES },\n      'attributes.caller_phone': { $exists: true, $nin: [null, ''] }\n    };\n    winston.info(`[phone-channels-migration] Voice VXML Starting...`);\n    const voiceVxmlResult = await updateManyWithNormalizedPhone(voiceVxmlFilter, (doc) => doc.attributes?.caller_phone);\n    winston.info(`[phone-channels-migration] Voice VXML: matched ${voiceVxmlResult.matched}, modified ${voiceVxmlResult.modified}`);\n\n    // WhatsApp\n    const wabFilter = {\n      'channel.name': 'whatsapp',\n      createdBy: { $regex: /^wab-/ }\n    };\n    winston.info(`[phone-channels-migration] WhatsApp Starting...`);\n    const wabResult = await updateManyWithNormalizedPhone(wabFilter, (doc) =>\n      doc.createdBy && doc.createdBy.startsWith('wab-') ? doc.createdBy.replace(/^wab-/, '') : null\n    );\n    winston.info(`[phone-channels-migration] WhatsApp: matched ${wabResult.matched}, modified ${wabResult.modified}`);\n\n    // SMS-Twilio\n    const smsFilter = {\n      'channel.name': 'sms-twilio',\n      createdBy: { $regex: /^sms-twilio-/ }\n    };\n    winston.info(`[phone-channels-migration] SMS-Twilio Starting...`);\n    const smsResult = await updateManyWithNormalizedPhone(smsFilter, (doc) =>\n      doc.createdBy && doc.createdBy.startsWith('sms-twilio-') ? doc.createdBy.replace(/^sms-twilio-/, '') : null\n    );\n    winston.info(`[phone-channels-migration] SMS-Twilio: matched ${smsResult.matched}, modified ${smsResult.modified}`);\n\n  } catch (err) {\n    winston.error('[phone-channels-migration] Error:', err);\n    throw err;\n  }\n}\n\nmodule.exports = { up };\n"
  },
  {
    "path": "models/actionsConstants.js",
    "content": "module.exports = {\n    CHAT_ACTION_MESSAGE : {\n        AGENT : \"\\\\agent\",\n        CLOSE : \"\\\\close\",    \n    }\n}\n    "
  },
  {
    "path": "models/analyticMessagesResult.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar AnalyticMessagesResultSchema = new Schema({\n  count: {\n    type: Number,\n     required: true\n  }}, { collection: 'messages' }\n);\n\nvar analyticMessagesResult= mongoose.model('analyticMessagesResultSchema', AnalyticMessagesResultSchema);\n\n\n\nmodule.exports = analyticMessagesResult;\n"
  },
  {
    "path": "models/analyticProject_usersResult.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar AnalyticProject_UsersResultSchema = new Schema({\n  count: {\n    type: Number,\n     required: true\n  }}, { collection: 'project_users' }\n);\n\nvar analyticProject_UsersResult= mongoose.model('analyticProject_UsersResult', AnalyticProject_UsersResultSchema);\n\n\n\nmodule.exports = analyticProject_UsersResult;\n"
  },
  {
    "path": "models/analyticResult.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n// mongoose.set('debug', true);\nvar winston = require('../config/winston');\nlet config = require('../config/database');\n\nvar databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\n\nvar readPreference = process.env.ANALYTICS_READ_PREFERENCE ||  \"primary\";\nwinston.info(\"Annalytics readPreference: \" + readPreference);\n\nvar conn = mongoose.createConnection(databaseUri, { \"autoIndex\": true, readPreference: readPreference});\t\n\n\nvar AnalyticResultSchema = new Schema({\n  // _id: {\n  //   type: String,\n  //   required: true\n  // },\n  count: {\n    type: Number,\n     required: true\n  }}, { collection: 'requests' }\n);\n\nvar analyticResult= conn.model('analyticResult', AnalyticResultSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  analyticResult.syncIndexes();\n  winston.verbose(\"analyticResult syncIndexes\")\n}\n\n\nmodule.exports = analyticResult;\n"
  },
  {
    "path": "models/analytics.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\nvar AnalyticsSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true,\n  },\n  type: {\n    type: String,\n    required: true\n  },\n  date: {\n    type: String,\n    required: true\n    //default: () => new Date().setHours(0, 0, 0, 0)\n  },\n  keys: {\n    type: Object,\n    required: true\n  }\n}, {\n  timestamps: true\n})\n\n\nconst Analytics = mongoose.model('Analytics', AnalyticsSchema)\nmodule.exports = Analytics\n\n"
  },
  {
    "path": "models/auth.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar bcrypt = require('bcrypt-nodejs');\n\nvar AuthSchema = new Schema({\n    // fullname: {\n    //     type: String,\n    //     required: false\n    //   },\n      providerId: {\n        type: String,\n        default: 'password',\n        required: true\n     },\n     subject: {\n        type: String,\n        required: true\n     },\n     email: {\n        type: String,        \n     },\n     password: {\n        type: String,\n        required: false,\n        // required: true,\n        // https://stackoverflow.com/questions/12096262/how-to-protect-the-password-field-in-mongoose-mongodb-so-it-wont-return-in-a-qu\n        // select: false\n    },\n    //   id_project: {\n    //     type: String,\n    //     required: true\n    //   }      \n    }, {\n        timestamps: true\n      }\n);\n\nAuthSchema.pre('save', function (next) {\n    var user = this;\n    if (this.isModified('password') || this.isNew) {\n        bcrypt.genSalt(10, function (err, salt) {\n            if (err) {\n                return next(err);\n            }\n            bcrypt.hash(user.password, salt, null, function (err, hash) {\n                if (err) {\n                    return next(err);\n                }\n                user.password = hash;\n                next();\n            });\n        });\n    } else {\n        return next();\n    }\n});\n\nAuthSchema.methods.comparePassword = function (passw, cb) {\n    bcrypt.compare(passw, this.password, function (err, isMatch) {\n        if (err) {\n            return cb(err);\n        }\n        cb(null, isMatch);\n    });\n};\nmodule.exports = mongoose.model('auth', AuthSchema);\n"
  },
  {
    "path": "models/bot.1.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n// var uuid = require('node-uuid');\n// require('mongoose-uuid2')(mongoose);\n// var UUID = mongoose.Types.UUID;\n\n// var id = mongoose.Types.ObjectId();\n\nvar BotSchema = new Schema({\n  // bot_id: { \n  //   type: UUID,\n  //   default: uuid.v4 \n  // },\n  fullname: {\n    type: String,\n    required: true\n  },\n  id_faq_kb: {\n    type: String,\n    // required: true\n  },\n  id_project: {\n    type: String,\n    required: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n}, {\n    timestamps: true\n  }\n);\n\nmodule.exports = mongoose.model('bot', BotSchema);\n"
  },
  {
    "path": "models/channel.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar ChannelSchema = new Schema({\n  name: { //ex: chat21\n    type: String,\n    required: true\n  }\n},{ _id : false });\n\nvar channel = mongoose.model('channel', ChannelSchema);;\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  channel.syncIndexes();\n  winston.verbose(\"channel syncIndexes\")\n}\n\n\nmodule.exports = channel;\n"
  },
  {
    "path": "models/channelConstants.js",
    "content": "module.exports = {\n        CHAT21 : 'chat21',\n        FACEBOOK : 'facebook',\n        TELEGRAM : 'telegram',\n        WHATSAPP : 'whatsapp',\n        FORM : 'form',   \n        EMAIL : 'email',                                \n}\n"
  },
  {
    "path": "models/chatbotTemplates.js",
    "content": "module.exports = {\n    chatbot: {\n        default: 'blank',\n        templates: ['empty', 'blank', 'handoff', 'example']\n    },\n    webhook: {\n        default: 'blank_webhook',\n        templates: ['empty', 'blank_webhook']\n    },\n    copilot: {\n        default: 'official_copilot',\n        templates: ['empty', 'blank_copilot', 'official_copilot']\n    },\n    voice: {\n        default: 'blank_voice',\n        templates: ['empty', 'blank_voice']\n    },\n    voice_twilio: {\n        default: 'blank_voice_twilio',\n        templates: ['empty', 'blank_voice_twilio']\n    }\n}\n"
  },
  {
    "path": "models/chatbotTypes.js",
    "content": "module.exports = {\n    CHATBOT: 'chatbot',\n    WEBHOOK: 'webhook',\n    COPILOT: 'copilot',\n    VOICE: 'voice'\n}\n"
  },
  {
    "path": "models/contact.js",
    "content": "const mongoose = require('mongoose');\nconst Schema = mongoose.Schema;\n\nconst ContactSchema = new Schema({\n  phone: {\n    type: String,\n    required: false,\n    trim: true\n  },\n  email: {\n    type: String,\n    required: false,\n    trim: true,\n    lowercase: true\n  },\n  external_id: {\n    type: String,\n    required: false,\n    trim: true\n  }\n}, {\n  timestamps: false,\n  _id: false\n});\n\nmodule.exports = ContactSchema;"
  },
  {
    "path": "models/department.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar routingConstants = require('../models/routingConstants');\nvar winston = require('../config/winston');\nvar TagSchema = require(\"../models/tag\");\nvar GroupMemberSchema = require(\"../models/groupMemberSchama\");\n\n\nvar DepartmentSchema = new Schema({\n  id_bot: {\n    type: String,\n  },\n  bot_only: { //deprecated\n    type: Boolean,\n    // required: true\n  },\n  routing: {\n    type: String,\n    default:routingConstants.POOLED,\n    // required: true\n  },\n  name: {\n    type: String,\n    required: true,\n    index:true\n  },\n  description: {\n    type: String,\n    // index:true\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  id_group: {\n    type: String,\n  },\n  groups: [GroupMemberSchema],\n  // used??\n  online_msg: {\n    type: String,\n  },\n  offline_msg: {\n    type: String,\n  },\n  default: {\n    type: Boolean,\n    default:false,\n    index: true\n    // required: true\n  },\n  tags: [TagSchema],\n  status: {\n    type: Number,\n    default: 1,     // 1: enabled; 0 hidden for widget; -1 hidden for the dashboard; \n    index: true\n    // required: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true,\n  toJSON: { virtuals: true }\n}\n);\n\nDepartmentSchema.virtual('bot', {\n  ref: 'faq_kb', // The model to use\n  localField: 'id_bot', // Find people where `localField`\n  foreignField: '_id', // is equal to `foreignField`\n  justOne: true,\n  //options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n});\n\nDepartmentSchema.virtual('hasBot').get(function () {\n  // winston.debug(\"department hasBot virtual called\");\n  if (this.id_bot!=undefined) {\n    return true;\n  }else {\n    return false;\n  }\n});\n\n\n// query = { default: true, id_project: projectid };\nDepartmentSchema.index({ id_project: 1, default: 1 }); \n\nvar department = mongoose.model('department', DepartmentSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  department.syncIndexes();\n  winston.verbose(\"department syncIndexes\")\n}\n\n\nmodule.exports = department;\n"
  },
  {
    "path": "models/faq.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar { nanoid } = require(\"nanoid\");\nconst uuidv4 = require('uuid/v4');\n\nvar defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || \"none\";\nlet trashExpirationTime = Number(process.env.CHATBOT_TRASH_TTL_SECONDS) || 60 * 60 * 24 * 30; // 30 days\n\nvar FaqSchema = new Schema({\n  // _id: {\n  //   type: mongoose.Schema.Types.ObjectId,\n  //   index: true,\n  //   required: true,\n  //   auto: true,\n  // },\n  id_faq_kb: {\n    type: String,\n    index: true\n  },\n  intent_id: {\n    type: String,\n    required: false,\n    index: true,\n    default: function () {\n      return uuidv4();\n    }\n  },\n  intent_display_name: { //documentare\n    type: String,\n    required: false,\n    index: true,\n    default: function () {\n      return nanoid(6);\n    }\n  },\n  question: {\n    type: String,\n    required: false\n  },\n\n  webhook_enabled: { //usa questo\n    type: Boolean,\n    required: false,\n    default: false,\n  },\n  answer: {\n    type: String,\n    //required: true\n    required: false\n  },\n  reply: {\n    type: Object,\n    required: false,\n  },\n  enabled: {\n    type: Boolean,\n    required: false,\n    default: true,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  topic: {\n    type: String,\n    default: \"default\",\n    index: true\n    // required: true\n  },\n  status: {\n    type: String,\n    default: \"live\",\n    index: true\n    // required: true\n  },\n  language: {\n    type: String,\n    required: false,\n    index: true\n  },\n\n  //   \"stats\":{  \n  //     \"conversation_count\":2,\n  //     \"all_done_count\":0,\n  //     \"wait_for_team_count\":2\n  //  }\n\n  createdBy: {\n    type: String,\n    required: true\n  },\n  form: {\n    type: Object,\n    required: false\n  },\n  actions: {\n    type: Array,\n    required: false\n  },\n  attributes: {\n    type: Object,\n  },\n  agents_available: {\n    type: Boolean,\n    required: false,\n    default: function () {\n      return this.isNew ? false : undefined;\n    },\n  },\n  trashed: {\n    type: Boolean,\n    index: true\n  },\n  trashedAt: {\n    type: Date,\n    required: false\n  },\n}, {\n  timestamps: true,\n  toJSON: { virtuals: true } //used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html\n}\n);\n\n\nFaqSchema.virtual('faq_kb', {\n  ref: 'faq_kb', // The model to use\n  localField: 'id_faq_kb', // Find people where `localField`\n  foreignField: '_id', // is equal to `foreignField`\n  justOne: false,\n  //options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n});\n\nFaqSchema.index({ id_project: 1, id_faq_kb: 1, question: 1 });\n\n// https://docs.mongodb.com/manual/core/index-text/\n// https://docs.mongodb.com/manual/tutorial/specify-language-for-text-index/\n// https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages\n\n// In testing...\n// FaqSchema.index({ question: \"text\", answer: \"text\" }, { weights: { question: 5, answer: 3 } })\n\n// FaqSchema.statics = {\n//   searchPartial: function (q, callback) {\n//     return this.find({\n//       $or: [\n//         { \"question\": new RegExp(q, \"gi\") },\n//         { \"answer\": new RegExp(q, \"gi\") },\n//       ]\n//     }, callback)\n//   },\n\n//   searchFull: function (q, callback) {\n//     return this.find({\n//       $text: { $search: q, $caseSensitive: false }\n//     }, callback)\n//   },\n\n//   searchFull: function (q, callback) {\n//     return this.find({\n//       $text: { $search: q, $caseSensitive: false }\n//     }, callback)\n//   },\n\n//   search: function (q, callback) {\n//     this.searchFull(q, (err, data) => {\n//       if (err) return callback(err, data);\n//       if (!err && data.length) return callback(err, data);\n//       if (!err && data.length === 0) return this.searchPartial(q, callback);\n//     });\n//   },\n\n// }\n\n// FaqSchema.index({ question: 'text' },\n//   { \"name\": \"faq_fulltext\", \"default_language\": defaultFullTextLanguage, \"language_override\": \"language\" }); // schema level\n  \nFaqSchema.index({ id_faq_kb: 1, question: \"text\" },\n  { name: \"faq_fulltext\", default_language: defaultFullTextLanguage, language_override: \"language\" }\n);\n\n//  FaqSchema.index({question: 'text', answer: 'text'},\n//  {\"name\":\"faq_fulltext\",\"default_language\": defaultFqullTextLanguage,\"language_override\": \"language\", weights: {question: 10,answer: 1}}); // schema level\n\n\nFaqSchema.index({ id_project: 1, id_faq_kb: 1, intent_display_name: 1 }, { unique: true });\nFaqSchema.index({ id_project: 1, id_faq_kb: 1, intent_id: 1 }, { unique: true });\n\nFaqSchema.index(\n  { \"actions.namespace\": -1 },\n  { partialFilterExpression: { \"actions.namespace\": { $exists: true } } }\n);\n\nFaqSchema.index(\n  { trashedAt: 1 },\n  { expireAfterSeconds: trashExpirationTime }\n);\n\nvar faq = mongoose.model('faq', FaqSchema);\n\nfaq.on('index', function (error) {\n  // \"_id index cannot be sparse\"\n  winston.debug('index', error);\n});\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  faq.syncIndexes();\n  winston.verbose(\"faq syncIndexes\")\n}\n\n\nmodule.exports = faq;\n"
  },
  {
    "path": "models/faq_kb.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nconst uuidv4 = require('uuid/v4');\nvar winston = require('../config/winston');\nconst { stringify } = require('uuid');\n\nvar defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || \"none\";\nlet trashExpirationTime = Number(process.env.CHATBOT_TRASH_TTL_SECONDS) || 60 * 60 * 24 * 30; // 30 days\n\nvar Faq_kbSchema = new Schema({\n  name: {\n    type: String,\n    required: true,\n    index: true\n  },\n  description: {\n    type: String,\n    // index:true\n  },\n  url: {\n    type: String,\n    // required: true\n  },\n  webhook_url: {\n    type: String,\n    // required: true\n  },\n  webhook_enabled: {\n    type: Boolean,\n    required: false,\n    default: false,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  // kbkey_remote: { //serve?\n  //   type: String,\n  // },  \n  type: {\n    type: String,\n    default: 'internal',\n    index: true\n  },\n  subtype: {\n    type: String,\n    default: function() {\n      return this.type === 'tilebot' ? 'chatbot' : undefined;\n    },\n    index: true\n  },\n  // external: {\n  //   type: Boolean,\n  //   default: false\n  // },\n  trashed: {\n    type: Boolean,\n    index: true\n  },\n  trashedAt: {\n    type: Date,\n    required: false\n  },\n  secret: {\n    type: String,\n    required: true,\n    default: uuidv4(),\n    select: false\n  },\n  language: {\n    type: String,\n    required: false,\n    default: 'en'\n    // index: true\n  },\n  attributes: {\n    type: Object,\n  },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  public: {\n    type: Boolean,\n    required: false,\n    default: false,\n    index: true\n  },\n  certified: {\n    type: Boolean,\n    required: false,\n    default: false,\n    index: true\n  },\n  mainCategory: {\n    type: String,\n    required: false\n  },\n  intentsEngine: {\n    type: String,\n    required: false,\n    default: 'none'\n  },\n  tags: [{\n    type: String\n  }],\n  score: {\n    type: Number,\n    required: false,\n    index: true,\n    default: 0\n  },\n  // publishedBy: {\n  //   type: String,\n  // },\n  publishedBy: {\n    type: Schema.Types.ObjectId,\n    ref: 'user',\n    required: false\n  },\n  publishedAt: {\n    type: Date\n  },\n  trained: {\n    type: Boolean,\n    default: true\n  },\n  short_description: {\n    type: String,\n    required: false\n  },\n  title: {\n    type: String,\n    required: false\n  },\n  certifiedTags: {\n    type: Array,\n    required: false\n  },\n  agents_available: {\n    type: Boolean,\n    required: false,\n    default: function () {\n      return this.isNew ? false : undefined;\n    },\n  },\n  slug: {\n    type: String,\n    required: false,\n    index: true\n  },\n  modified: {\n    type: Boolean,\n    required: false\n  },\n  root_id: {\n    type: String,\n    required: false\n  },\n  release_note: {\n    type: String,\n    required: false\n  }\n},{\n  timestamps: true\n});\n\n\nFaq_kbSchema.pre(\"save\", async function (next) {\n  // Check if the document is new and if the slug has not been set manually\n  if (this.isNew) {\n\n    let baseSlug = this.slug || generateSlug(this.name);\n    let uniqueSlug = baseSlug;\n\n    const existingDocs = await mongoose.model(\"faq_kb\").find({\n      id_project: this.id_project,\n      slug: { $regex: `^${baseSlug}(?:-\\\\d+)?$` }\n    }, { slug: 1 });\n\n    if (existingDocs.length > 0) {\n      let maxNumber = 0;\n      existingDocs.forEach(doc => {\n        const match = doc.slug.match(new RegExp(`^${baseSlug}-(\\\\d+)$`));\n        if (match) {\n          const num = parseInt(match[1], 10);\n          if (num > maxNumber) maxNumber = num;\n        }\n      });\n\n      if (existingDocs.some(doc => doc.slug === baseSlug)) {\n        uniqueSlug = `${baseSlug}-${maxNumber + 1}`;\n      }\n    }\n\n    this.slug = uniqueSlug;\n  }\n\n  next();\n});\n\nFaq_kbSchema.pre('findOneAndUpdate', async function (next) {\n\n  const update = this.getUpdate();\n  const isUnsetSlug = update?.$unset?.slug !== undefined;\n\n  // $unset.slug is used only on publishing. In this case, skip the slug change and the set of trashedAt\n  if (update.trashed === true && !isUnsetSlug) {\n\n    const docToUpdate = await this.model.findOne(this.getQuery());\n    const timestamp = Date.now();\n\n    if (docToUpdate && docToUpdate.slug) {\n      let slug;\n      slug = docToUpdate.slug;\n      update.trashedAt = new Date();\n      update.slug = `${slug || 'undefined'}-trashed-${timestamp}`;\n    }\n    this.setUpdate(update);\n  }\n\n  next();\n\n});\n\nFaq_kbSchema.post('findOneAndUpdate', async function (doc) {\n  if (doc && doc.trashed === true) {\n    botEvent.emit('faqbot.update.virtual.delete', doc)\n  }\n})\n\n\nFaq_kbSchema.virtual('fullName').get(function () {\n  // winston.debug(\"faq_kb fullName virtual called\");\n  return (this.name);\n});\n\nFaq_kbSchema.index({certified: 1, public: 1}); //suggested by atlas\n\n\nFaq_kbSchema.index(\n  {name: 'text', description: 'text', \"tags\": 'text'},  \n  {\"name\":\"faqkb_fulltext\",\"default_language\": defaultFullTextLanguage,\"language_override\": \"language\"}); // schema level\n\nFaq_kbSchema.index(\n  { id_project: 1, slug: 1 },\n  { unique: true, partialFilterExpression: { slug: { $exists: true } } }\n);\n\nFaq_kbSchema.index(\n  { trashedAt: 1 },\n  { expireAfterSeconds: trashExpirationTime }\n);\n\n\nvar faq_kb = mongoose.model('faq_kb', Faq_kbSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  faq_kb.syncIndexes();\n  winston.verbose(\"faq_kb syncIndexes\")\n}\n\nfunction generateSlug(name) {\n  return name\n    .toLowerCase()\n    .trim()\n    .normalize(\"NFD\") // Normalize characters with accents\n    .replace(/[\\u0300-\\u036f]/g, \"\") // Removes diacritics (e.g. à becomes a)\n    .replace(/[^a-z0-9\\s-_]/g, \"\") // Remove special characters\n    .replace(/\\s+/g, \"-\") // Replaces spaces with dashes\n    .replace(/_/g, \"-\")\n    .replace(/-+/g, \"-\"); // Removes consecutive hyphens\n}\n\nmodule.exports = faq_kb\n\n\n\n// Import botEvent after model declaration to avoid circular dependency issues\nconst botEvent = require('../event/botEvent');"
  },
  {
    "path": "models/firebaseSetting.js",
    "content": "// var mongoose = require('mongoose');\n// var Schema = mongoose.Schema;\n\n// var FirebaseSettingSchema = new Schema({\n  \n\n//   private_key: {\n//     type: String,\n//     required: true,\n//     select: false\n//   },\n//   client_email: {\n//     type: String,\n//     required: true,\n//     select: false\n//   },\n//   firebase_project_id: {\n//     type: String,\n//     required: true,\n//     select: false\n//   }\n  \n// },{\n//   timestamps: true\n// }\n// );\n\n// module.exports = mongoose.model('firebaseSetting', FirebaseSettingSchema);\n"
  },
  {
    "path": "models/flowLogs.js",
    "content": "const mongoose = require('mongoose');\nconst Schema = mongoose.Schema;\n\nconst RowSchema = new Schema(\n  {\n    text: {\n      type: String,\n      required: true\n    },\n    level: {\n      type: String,\n      required: true\n    },\n    timestamp: {\n      type: Date,\n      required: true,\n      default: Date.now\n    }\n  }\n);\n\nconst FlowLogsSchema = new Schema(\n  {\n    id_project: {\n      type: String,\n      required: false,\n    },\n    request_id: {\n      type: String,\n      required: false,\n    },\n    webhook_id: {\n      type: String,\n      required: false,\n    },\n    shortExp: {\n      type: Date,\n      required: false\n    },\n    longExp: {\n      type: Date,\n      required: false\n    },\n    level: {\n      type: String,\n      required: true,\n    },\n    rows: {\n      type: [RowSchema],\n      required: false,\n    },\n  }, {\n    timestamps: true\n  }\n);\n\nFlowLogsSchema.index({ request_id: 1 }, { unique: true });\nFlowLogsSchema.index({ webhook_id: 1 });\nFlowLogsSchema.index({ shortExp: 1 }, { expireAfterSeconds: 300 });  // 5 minutes\nFlowLogsSchema.index({ longExp: 1 }, { expireAfterSeconds: 1800 }); // 30 minutes\n\n// FlowLogsSchema.pre('findOneAndUpdate', async function (next) {\n//   const update = this.getUpdate();\n\n//   if (update.$push && update.$push.rows) {\n//     const doc = await this.model.findOne(this.getQuery());\n\n//     if (!doc) {\n//       update.$push.rows.index = 0;\n//     } else {\n//       update.$push.rows.index = doc.rows.length;\n//     }\n//   }\n//   next();\n// });\n\nconst FlowLogs = mongoose.model('FlowLogs', FlowLogsSchema);\n\nmodule.exports = { FlowLogs };\n"
  },
  {
    "path": "models/group.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar GroupSchema = new Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  members: {\n    type: Array, \"default\": [],\n    index: true\n    // required: true\n  },\n  id_project: {\n    type: String,\n    index: true\n    // required: true\n  },\n  enabled: {\n    type: Boolean,\n    default: true,\n    index: true\n  },\n  trashed: {\n    type: Boolean,\n    index: true\n  },\n  attributes: {\n    type: Object,\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n}, {\n    timestamps: true\n  }\n);\n\n// var Group = mongoose.model('Group', GroupSchema);\n\n// GroupSchema.statics.findByProjectId = function (projectId, callback) {\n//   group.find({ \"id_project\": projectId, trashed: false }, function (err, groups) {\n//     winston.debug('GROUP MODEL - FIND BY PROJECT ID - ERR ', err)\n//     callback(err, groups);\n//   });\n// };\n\nmodule.exports = mongoose.model('group', GroupSchema);\n\n// GroupSchema.statics.findByProjectId = function (projectId, callback) {\n//   group.find({ \"id_project\": projectId, trashed: false }, function (err, groups) {\n//     winston.debug('GROUP MODEL - FIND BY PROJECT ID - ERR ', err)\n//     callback(err, groups);\n//   });\n// };\n\n// RETURN ONLY THE GROUP WITH TRASHED = FALSE\n// GroupSchema.statics.findByProjectId = function (projectId, callback) {\n//   winston.debug('-1')\n//   winston.debug('GROUP MODEL - FIND BY PROJECT ID');\n\n//   GroupSchema.find({ \"id_project\": projectId, trashed: false }, function (err, groups) {\n//     winston.debug('-3');\n    \n//     callback(err, groups);\n//   });\n// };\n"
  },
  {
    "path": "models/groupMemberSchama.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar GroupMemberSchema = new Schema({\n  \n  group_id: {\n    type: String,\n    required: true\n  },  \n  percentage: {\n    type: Number,\n    required: true,\n  }\n}\n);\n\n\nmodule.exports = GroupMemberSchema;\n"
  },
  {
    "path": "models/integrations.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar IntegrationsSchema = new Schema({\n    id_project: {\n        type: String,\n        required: true,\n        index: true\n    },\n    name: {\n        type: String,\n        required: true\n    },\n    value: {\n        type: Object,\n        required: true,\n        default: {}\n    }\n})\n\n\nmodule.exports = mongoose.model('integration', IntegrationsSchema);\n"
  },
  {
    "path": "models/kbConstants.js",
    "content": "const kbTypes = {\n        URL: 'url',\n        TEXT: 'text',\n        FAQ: 'faq',\n        PDF: 'pdf',\n        DOCX: 'docx'\n};\n\n\nmodule.exports = {\n        kbTypes\n};"
  },
  {
    "path": "models/kb_setting.js",
    "content": "let mongoose = require('mongoose');\nlet Schema = mongoose.Schema;\nlet winston = require('../config/winston');\n\nconst DEFAULT_UNANSWERED_TTL_SEC = 7 * 24 * 60 * 60; // 7 days\nconst DEFAULT_ANSWERED_TTL_SEC = 7 * 24 * 60 * 60; // 7 days\n\nfunction ttlSecondsFromEnv(raw, fallbackSec) {\n  if (raw == null || String(raw).trim() === '') return fallbackSec;\n  const n = Number(raw);\n  return Number.isFinite(n) && n >= 0 ? n : fallbackSec;\n}\n\nlet expireAfterSeconds = ttlSecondsFromEnv(\n  process.env.UNANSWERED_QUESTION_EXPIRATION_TIME,\n  DEFAULT_UNANSWERED_TTL_SEC\n);\nlet expireAnsweredAfterSeconds = ttlSecondsFromEnv(\n  process.env.ANSWERED_QUESTION_EXPIRATION_TIME,\n  DEFAULT_ANSWERED_TTL_SEC\n);\n\n\nconst EngineSchema = new Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  type: {\n    type: String,\n    required: true\n  },\n  apikey: {\n    type: String,\n    required: false\n  },\n  vector_size: {\n    type: Number,\n    required: true\n  },\n  index_name: {\n    type: String,\n    required: true\n  },\n  host: {\n    type: String,\n    required: false\n  },\n  port: {\n    type: String,\n    required: false\n  },\n  deployment: {\n    type: String,\n    required: false\n  },\n}, {\n  _id: false  // This is schema is always used as an embedded object inside NamespaceSchema\n})\n\nconst EmbeddingSchema = new Schema({\n  provider: {\n    type: String,\n    required: true\n  },\n  name: {\n    type: String,\n    required: true\n  },\n  dimension: {\n    type: Number,\n    reuired: true\n  },\n  url: {\n    type: String,\n    required: false\n  },\n  api_key: {\n    type: String,\n    required: false\n  }\n}, {\n  _id: false  // This is schema is always used as an embedded object inside NamespaceSchema\n})\n\nconst NamespaceSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true\n  },\n  id: {\n    type: String,\n    required: true\n  },\n  name: {\n    type: String,\n    required: true\n  },\n  preview_settings: {\n    type: Object,\n    required: true\n  },\n  default: {\n    type: Boolean,\n    default: false\n  },\n  hybrid: {\n    type: Boolean,\n    default: false\n  },\n  engine: {\n    type: EngineSchema,\n    required: true\n  },\n  embedding: {\n    type: EmbeddingSchema,\n    required: true\n  }\n}, {\n  timestamps: true\n})\n\nvar KBSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true,\n  },\n  name: {\n    type: String,\n    required: true\n  },\n  url: {\n    type: String,\n    required: false\n  },\n  source: {\n    type: String,\n    required: false\n  },\n  type: {\n    type: String,\n    required: false\n  },\n  content: {\n    type: String,\n    required: false\n  },\n  sitemap_origin_id: {\n    type: String,\n    required: false\n  },\n  sitemap_origin: {\n    type: String,\n    required: false\n  },\n  namespace: {\n    type: String,\n    required: false\n  },\n  status: {\n    type: Number,\n    required: false\n  },\n  scrape_type: {\n    type: Number,\n    required: false\n  },\n  scrape_options: {\n    type: Object,\n    required: false,\n    default: undefined,\n    tags_to_extract: {\n      type: Array,\n      required: false\n    },\n    unwanted_tags: {\n      type: Array,\n      required: false\n    },\n    unwanted_classnames: {\n      type: Array,\n      required: false\n    }\n  },\n  refresh_rate: {\n    type: String,\n    required: false\n  },\n  last_refresh: {\n    type: Date,\n    required: false\n  },\n  last_error: {\n    type: Object,\n    required: false\n  },\n  tags: {\n    type: Array,\n    default: undefined,\n    required: false\n  }\n}, {\n  timestamps: true\n})\n\nconst UnansweredQuestionSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  namespace: {\n    type: String,\n    required: true,\n    index: true\n  },\n  question: {\n    type: String,\n    required: true\n  },\n  request_id: {\n    type: String,\n    required: false,\n  },\n  sender: {\n    type: String,\n    required: false,\n  }\n},{\n  timestamps: true\n});\n\nconst AnsweredQuestionSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  namespace: {\n    type: String,\n    required: true,\n    index: true\n  },\n  question: {\n    type: String,\n    required: true\n  },\n  answer: {\n    type: String,\n    required: true\n  },\n  tokens: {\n    type: Number,\n    required: false\n  },\n  request_id: {\n    type: String,\n    required: false\n  }\n}, {\n  timestamps: true\n});\n\n// Add TTL index to automatically delete documents\nUnansweredQuestionSchema.index({ createdAt: 1 }, { expireAfterSeconds: expireAfterSeconds }); \nUnansweredQuestionSchema.index({ id_project: 1, namespace: 1, createdAt: -1 });\nUnansweredQuestionSchema.index({ question: \"text\" });\n\nAnsweredQuestionSchema.index({ createdAt: 1 }, { expireAfterSeconds: expireAnsweredAfterSeconds });\nAnsweredQuestionSchema.index({ id_project: 1, namespace: 1, createdAt: -1 });\nAnsweredQuestionSchema.index(\n  { question: \"text\", answer: \"text\" },\n  { weights: { question: 3, answer: 1 } }\n);\n\n\n\n// DEPRECATED !! - Start\nconst KBSettingSchema = new Schema({\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  gptkey: {\n    type: String,\n    //required: true\n  },\n  maxKbsNumber: {\n    type: Number,\n    default: 3\n  },\n  maxPagesNumber: {\n    type: Number,\n    default: 1000\n  },\n  kbs: [KBSchema]\n});\n// DEPRECATED !! - End\n\n\nKBSchema.index({ createdAt: -1, updatedAt: -1 })\nKBSchema.index({ id_project: 1, namespace: 1, updatedAt: -1 })\nKBSchema.index({ namespace: 1, type: 1 })\n\n\n// DEPRECATED\nconst KBSettings = mongoose.model('KBSettings', KBSettingSchema); \nconst Engine = mongoose.model('Engine', EngineSchema)\nconst Namespace = mongoose.model('Namespace', NamespaceSchema)\nconst KB = mongoose.model('KB', KBSchema)\nconst UnansweredQuestion = mongoose.model('UnansweredQuestion', UnansweredQuestionSchema)\nconst AnsweredQuestion = mongoose.model('AnsweredQuestion', AnsweredQuestionSchema)\n\n\nmodule.exports = {\n  KBSettings: KBSettings,\n  Namespace: Namespace,\n  Engine: Engine,\n  KB: KB,\n  UnansweredQuestion: UnansweredQuestion,\n  AnsweredQuestion: AnsweredQuestion\n}\n"
  },
  {
    "path": "models/label.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar LabelEntrySchema = new Schema({\n  lang: { \n    type: String,\n    required: true,\n    index:true\n  },\n  data: {\n    type: Object,\n    required: true\n  },\n  category: {\n    type: String,\n    required: false,\n    index: true\n  },\n  default: {   \n    type: Boolean,\n    // required: false,\n    // get: v => {\n    //   console.log(\"v\",v)\n    //   if (v) {\n    //     return v;\n    //   } else {\n    //     return false;\n    //   }\n    // },\n    required: true,\n    default: false,\n    index: true\n  },\n});\n\nvar LabelSchema = new Schema({\n  \n  data: { \n    //type: Array,\n    type: [LabelEntrySchema],\n    required: true,\n    //index: true\n  }, \n  attributes: {\n    type: Object,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },  \n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\n var label = mongoose.model('label', LabelSchema);\n\n\nmodule.exports = label;\n"
  },
  {
    "path": "models/labelSingle.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar LabelSingleSchema = new Schema({\n  \n  lang: { \n    type: String,\n    required: true,\n    index: true\n  },\n  key: { \n    type: String,\n    required: true,\n    index: true\n  },\n  message: { \n    type: String,\n    required: true,\n    index: true\n  },\n  attributes: {\n    type: Object,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },  \n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\n var label = mongoose.model('label', LabelSingleSchema);\n\n\nmodule.exports = label;\n"
  },
  {
    "path": "models/lead.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar LeadConstants = require('./leadConstants');\nvar TagSchema = require(\"../models/tag\");\nvar defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || \"none\";\n\n\n/*\nLeads are useful for representing logged-out users of your application. \nAs soon as a visitor starts a conversation with you, or replies to a visitor auto message, they become a lead. \nYou can follow up with leads via email if they share their email address with you.\n*/\n\nvar LeadSchema = new Schema({\n  \n  lead_id: { \n    type: String,\n    required: true,\n    index: true\n  },\n  fullname: {\n    type: String,\n    required: false\n  },\n  email: {\n    type: String,\n    required: false,\n    index: true\n  },\n  phone: {\n    type: String,\n    required: false\n  },\n  company: {\n    type: String,\n    required: false\n  },\n  note: {\n    type: String,\n    required: false\n  },\n  streetAddress: {\n    type: String,\n    required: false\n  },\n  city: {\n    type: String,\n    required: false\n  },\n  region: {\n    type: String,\n    required: false\n  },\n  zipcode: {\n    type: String,\n    required: false\n  },\n  country: {\n    type: String,\n    required: false\n  },\n  // tags: [TagSchema],\n  tags: [{\n    type: String\n  }],\n  // aggiungi location e togli flat fields\n  attributes: {\n    type: Object,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  // auth: {\n  //   type: Schema.Types.ObjectId,\n  //   ref: 'auth',\n  //   //required: true\n  // },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  status: {\n    type: Number,\n    required: false,\n    default: LeadConstants.NORMAL,  \n    index: true\n  }, \n  properties: {\n    type: Object,\n  },\n},{\n  timestamps: true\n}\n);\n\n// https://docs.mongodb.com/manual/core/index-text/\n// https://docs.mongodb.com/manual/tutorial/specify-language-for-text-index/\n// https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages\nLeadSchema.index({fullname: 'text', email: 'text'},\n {\"name\":\"lead_fulltext\",\"default_language\": defaultFullTextLanguage,\"language_override\": \"dummy\"}); // schema level\n\n  // suggested by atlas\nLeadSchema.index({status: 1, id_project: 1, createdAt: -1});\n\n\n\n var lead = mongoose.model('lead', LeadSchema);\n\n if (process.env.MONGOOSE_SYNCINDEX) {\n  lead.syncIndexes();\n  winston.verbose(\"lead syncIndexes\")\n}\n\nmodule.exports = lead;\n"
  },
  {
    "path": "models/leadConstants.js",
    "content": "module.exports = {\n        TEMP : 50,\n        NORMAL : 100,\n        DELETED: 1000\n}\n"
  },
  {
    "path": "models/location.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar LocationSchema = new Schema({\n  country: {\n    type: String,\n    required: false,\n    index: true\n  },\n  city: {\n    type: String,\n    required: false,\n    index: true\n  },\n  streetAddress: {\n    type: String,\n    required: false,\n    index: true\n  },\n  region: {\n    type: String,\n    required: false,\n    index: true\n  },\n  zipcode: {\n    type: String, \n    required: false,\n    index: true\n  },\n  ipAddress: {\n    type: String, \n    required: false,\n    index: true\n  },\n  // https://mongoosejs.com/docs/geojson.html\n  geometry: {\n    type: {\n      type: String, // Don't do `{ location: { type: String } }`\n      enum: ['Point'], // 'location.type' must be 'Point'\n      required: false\n    },\n    coordinates: {\n      type: [Number],\n      required: false\n    },    \n    // index: '2dsphere' // Create a special 2dsphere index on `City.location`\n  }  \n},{ _id : false }\n);\n\n\nmodule.exports = LocationSchema;\n"
  },
  {
    "path": "models/message.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Channel = require('../models/channel');\nvar MessageConstants = require('../models/messageConstants');\nvar defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || \"none\";\n\nvar MessageSchema = new Schema({\n  // messageId: {\n  //   type: String,\n  //   required: true,\n  //   // unique: true???\n  //   index: true\n  // },\n  sender: {\n    type: String,\n    required: true,\n    index: true\n  },\n  senderFullname: {\n    type: String,\n    required: false,\n    default: \"Guest\" // guest_here\n  },\n  recipient: {\n    type: String,\n    required: true,\n    index: true\n  },\n  recipientFullname: {\n    type: String,\n    required: false\n  },\n  type: {\n    type: String,\n    required: true,\n    default: 'text',\n    index: true\n  },\n  channel_type: {\n    type: String,\n    required: true,\n    default: MessageConstants.CHANNEL_TYPE.GROUP,\n    index: true\n  },\n  text: {\n    type: String,\n    // required: function() {\n    //   if (this.type === \"text\") {\n    //     return true;\n    //   }else {\n    //     return false;\n    //   }\n    // }\n  },\n  language: { //ISO 639-1 (Two letter codes) https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages\n    type: String,\n    required: false,\n    index:true\n  },\n  channel: {\n    type: Channel.schema,\n    default: function() {\n      return new Channel({name: 'chat21'});\n    }\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  status: {\n    type: Number,\n    default: 200,  //RECEIVED\n    required: true,\n    index: true\n  },\n  attributes: {\n    type: Object,\n  },\n  metadata: {\n    type: Object,\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\n\nMessageSchema.index({ recipient: 1, createdAt:1 }); \nMessageSchema.index({ id_project: 1, recipient:1, createdAt: 1 });\nMessageSchema.index({ recipient: 1, updatedAt:1 }); \nMessageSchema.index({ id_project: 1, recipient:1, updatedAt: 1 });\nMessageSchema.index({ id_project: 1, \"attributes._answerid\": 1 });\n\n\n\n// https://docs.mongodb.com/manual/core/index-text/\n// https://docs.mongodb.com/manual/tutorial/specify-language-for-text-index/\n// https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages\n//TODO cambiare dummy con language? attento che il codice deve essere compatibile\nMessageSchema.index({text: 'text'},\n {\"name\":\"message_fulltext\",\"default_language\": defaultFullTextLanguage, \"language_override\": \"dummy\"}); // schema level\n\n\nvar message = mongoose.model('message', MessageSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  message.syncIndexes();\n  winston.verbose(\"message syncIndexes\")\n}\n\nmodule.exports = message;\n"
  },
  {
    "path": "models/messageConstants.js",
    "content": "module.exports = {\n    CHAT_MESSAGE_STATUS : {\n        FAILED : -100,\n        SENDING : 0,\n        SENT : 100, //saved into sender timeline\n        DELIVERED : 150, //delivered to recipient timeline\n        RECEIVED : 200, //received from the recipient client\n        RETURN_RECEIPT: 250, //return receipt from the recipient client\n        SEEN : 300 //seen\n\n    },\n    CHANNEL_TYPE : {\n        GROUP : \"group\",\n        DIRECT : \"direct\",    \n    },\n\n    MESSAGE_TYPE : {\n        TEXT : \"text\",\n        IMAGE : \"image\",    \n        FRAME: \"frame\"\n    },\n\n\n    LABELS : {\n        EN : {\n            // JOIN_OPERATOR_MESSAGE : \"We are putting you in touch with an operator..\",\n            NO_AVAILABLE_OPERATOR_MESSAGE : \"Hello, no operators are available at the moment. Please leave a chat message, we will reply to you soon.\",\n            // TOUCHING_OPERATOR: \"We are putting you in touch with an operator. Please wait..\",\n            TOUCHING_OPERATOR: \"A new support request has been assigned to you\"\n            // THANKS_MESSAGE: \"Thank you for using our support system\",\n            // REOPEN_MESSAGE : \"Chat re-opened\"\n        },\n        IT : {\n            // JOIN_OPERATOR_MESSAGE : \"La stiamo mettendo in contatto con un operatore. Attenda...\",\n            NO_AVAILABLE_OPERATOR_MESSAGE : \"Salve al momento non è disponibile alcun operatore. Lasci un messaggio in chat, la contatteremo presto.\",\n            TOUCHING_OPERATOR: \"Una nuova richiesta di supporto è stata assegnata a te\"\n            // TOUCHING_OPERATOR :\"La stiamo mettendo in contatto con un operatore. Attenda...\",\n            // THANKS_MESSAGE: \"Grazie per aver utilizzato il nostro sistema di supporto\",\n            // REOPEN_MESSAGE : \"Chat riaperta\"\n        },\n        \"IT-IT\" : {\n            // JOIN_OPERATOR_MESSAGE : \"La stiamo mettendo in contatto con un operatore. Attenda...\",\n            NO_AVAILABLE_OPERATOR_MESSAGE : \"Salve al momento non è disponibile alcun operatore. Lasci un messaggio in chat, la contatteremo presto.\",\n            TOUCHING_OPERATOR: \"Una nuova richiesta di supporto è stata assegnata a te\"\n            // TOUCHING_OPERATOR :\"La stiamo mettendo in contatto con un operatore. Attenda...\",\n            // THANKS_MESSAGE: \"Grazie per aver utilizzato il nostro sistema di supporto\",\n            // REOPEN_MESSAGE : \"Chat riaperta\"\n        }\n    }\n}\n    "
  },
  {
    "path": "models/note.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar NoteSchema = new Schema({\n  \n  text: {\n    type: String,\n    required: true,\n    index: true\n  },    \n  attributes: {\n    type: Object,\n  },\n  // id_project: {\n  //   type: String,\n  //   required: true,\n  //   index: true\n  // },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\n var Note = mongoose.model('note', NoteSchema);\n\n\nmodule.exports = Note;\n"
  },
  {
    "path": "models/openai_kbs.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar OpenaiKbsSchema = new Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  url: {\n    type: String,\n    required: true\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  gptkey: {\n    type: String,\n    required: true\n  },\n  createdAt: {\n    type: Date,\n    default: Date.now,\n  }\n});\n\n\nmodule.exports = mongoose.model('OpenaiKbs', OpenaiKbsSchema);\n"
  },
  {
    "path": "models/pending-invitation.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\nvar winston = require('../config/winston');\n\nvar PendingInvitation = new Schema({\n  \n  email: {\n    type: String,\n    index:true\n    // required: true\n  },\n  id_project: {\n    type: String,\n    index:true\n    // required: true\n  },\n  role: {\n    type: String,\n    // required: true\n  },\n\n  // TODO initial status (available,unavailable) \n  createdBy: {\n    type: String,\n    required: true\n  }\n}, {\n    timestamps: true\n  }\n);\n\n\nvar pending= mongoose.model('pending-invitation', PendingInvitation);\n\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  pending.syncIndexes();\n  winston.verbose(\"pending syncIndexes\")\n}\n\nmodule.exports = pending;\n"
  },
  {
    "path": "models/permissionConstants.js",
    "content": "const OWNER_ROLE = [                                \n        ];\n\nconst ADMIN_ROLE = [                               \n                \"request_read_all\"\n        ];\nconst AGENT_ROLE= [\n                \"request_read_group\"\n        ];\n\n// console.log(\"ADMIN_ROLE\", ADMIN_ROLE.concat(AGENT_ROLE));\nmodule.exports = {\n        \"agent\": AGENT_ROLE,\n        \"admin\": ADMIN_ROLE.concat(AGENT_ROLE),\n        \"owner\": OWNER_ROLE.concat(ADMIN_ROLE).concat(AGENT_ROLE) \n\n}\n// const ADMIN_ROLE = Object.assign({}, ADMIN_ROLE, AGENT_ROLE)  \n// const ADMIN_ROLE = { ...ADMIN_ROLE, ...AGENT_ROLE }  \n"
  },
  {
    "path": "models/presence.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar PresenceSchema = new Schema({\n  status: {\n    type: String,\n    default: 'offline',  //online\n    required: true,\n    index: true\n  },\n  changedAt: {\n    type: Date,\n    default: new Date()\n  },\n}\n,{ _id : false });\n\n\nmodule.exports = PresenceSchema;\n"
  },
  {
    "path": "models/profile.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nlet isCommunity = false;\nif (process.env.COMMUNITY_VERSION === true || process.env.COMMUNITY_VERSION === 'true') {\n  isCommunity = true;\n}\n\nvar ProfileSchema = new Schema({\n  name: {\n    type: String,\n    default: function() {\n      return (isCommunity) ? 'Custom' : 'Sandbox';\n    },\n    index: true\n  },\n  trialDays: {\n    type: Number,\n    default: 14\n  },\n  agents: {\n    type: Number,\n    default: function() {\n      return (isCommunity) ? 1000 : 0;\n    }\n  },\n  type: {\n    type: String,\n    default: function() {\n      return (isCommunity) ? 'payment' : 'free';\n    }\n  },\n  quotes: {\n    type: Object,\n    default: function() {\n      if (isCommunity) {\n        return {\n          chatbots: 10000,\n          kbs: 10000,\n          namespace: 10000,\n        }\n      }\n      return undefined;\n    }\n  },\n  customization: {\n    type: Object,\n    default: function() {\n      if (isCommunity) {\n        return {\n          copilot: true,\n          webhook: true,\n          widgetUnbranding: true,\n          smtpSettings: true,\n          knowledgeBases: true,\n          reindex: true,\n          messanger: true,\n          telegram: true,\n          chatbot: true\n        };\n      }\n      return undefined;\n    }\n  },\n  subStart: {\n    type: Date,\n    default: function() {\n      if (isCommunity) {\n        return new Date();\n      }\n      return undefined;\n    }\n  },\n  subEnd: {\n    type: Date,\n    default: function() {\n      if (isCommunity) {\n        // Set date to 31 December 2099\n        return new Date('2099-12-31T23:59:59.999Z');\n      }\n      return undefined;\n    }\n  },\n  subscriptionId:  {\n    type: String,\n  },\n  extra1:  {\n    type: String,\n  },\n  extra2:  {\n    type: String,\n  },\n  extra3:  {\n    type: String,\n  },\n  subscription_creation_date: {\n    type: Date,\n  },\n  last_stripe_event:  {\n    type: String,\n  },\n\n}\n,{ _id : false });\n\nvar profile =mongoose.model('profile', ProfileSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  profile.syncIndexes();\n  winston.verbose(\"profile syncIndexes\")\n}\n\n\nmodule.exports = profile;\n"
  },
  {
    "path": "models/project.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Profile = require('../models/profile');\nvar Channel = require('../models/channel');\nvar pjson = require('../package.json');\nconst uuidv4 = require('uuid/v4');\n\n\n\nvar trialEnabled = process.env.TRIAL_MODE_ENABLED || false;\nwinston.debug(\"trial mode enabled: \"+trialEnabled );\n\nif (trialEnabled) {\n  winston.info(\"Trial mode enabled\");\n}\n\nvar ChatLimitOn = process.env.SMART_ASSIGNMENT_CHAT_LIMIT_ON_DEFAULT_PROJECT || false;\nwinston.debug(\"ChatLimitOn: \"+ChatLimitOn );\n\n\nif (ChatLimitOn) {\n  winston.info(\"Smart Assignment Chat Limit On as default value for projects\");\n}\n\nvar ProjectSchema = new Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  activeOperatingHours: {\n    type: Boolean,\n  },\n  operatingHours: {\n    type: Object,\n  },\n  timeSlots: {\n    type: Object,\n  },\n  settings: {\n    type: Object,\n    default: function () {\n      var defaultValue = {};\n\n      if (ChatLimitOn) {\n        defaultValue.chat_limit_on = true;\n      }\n      \n      return defaultValue; \n    }\n  },\n  widget: {\n    type: Object,\n  },\n  publicKey: {\n    type: String,\n    select: false\n  },\n  privateKey: {\n    type: String,\n    select: false\n  },\n  status: {\n    type: Number,\n    required: true,\n    default: 100,\n    index: true,\n    // select: false\n  }, \n  jwtSecret: {\n    type: String,\n    select: false\n  },\n  attributes: {\n    type: Object,\n  },\n  // apiKey: { //You do want to block anonymous traffic. https://cloud.google.com/endpoints/docs/openapi/when-why-api-key#:~:text=API%20keys%20identify%20an%20application's,patterns%20in%20your%20API's%20traffic\n  //   type: String,\n  //   select: false,\n  //   default: function () {\n  //     return uuidv4(); \n  //     //aggiungere env.GLOBAL_API_KEY che deve essere usato da code e dialogflow\n  //   }\n  // },\n  profile: {\n    type: Profile.schema,\n    default: function () {\n      return new Profile();\n    }\n  },\n  versions: {\n    type: Number,\n    default: function() {\n      // try {\n      //   var version = pjson.version;\n      //   var versionNumber = parseInt(version.split(\".\").join(\"\"));\n      //   //console.log(\"versionNumber\",versionNumber);\n      //   return versionNumber;\n      // } catch(e) {\n        return 20115; //2.01.15\n      // }    \n    }\n  },\n  channels: {\n    type: [Channel.schema],\n    default: function () {\n      return [new Channel({ name: 'chat21' })];\n    }\n  },\n  organization: {\n    type: String,\n  },\n  ipFilterEnabled:{\n    type: Boolean,\n    default: false\n  },\n  ipFilter: [{\n    type: String\n  }],\n  ipFilterDenyEnabled:{\n    type: Boolean,\n    default: false\n  },\n  ipFilterDeny: [{\n    type: String\n  }],\n  bannedUsers: [{\n     id: String, ip: String\n  }],\n  // defaultLanguage: {\n  //   type: String,\n  //   required: true,\n  //   default: \"EN\",\n  //   index: true\n  // },\n  createdBy: {\n    type: String,\n    required: true\n  }\n}, {\n    timestamps: true,\n    toJSON: { virtuals: true } //used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html\n  }\n);\n\nProjectSchema.virtual('trialExpired').get(function () {\n\n\n  if (trialEnabled === false) {\n    return false;\n  }\n\n  // https://stackoverflow.com/questions/6963311/add-days-to-a-date-object\n  let now = new Date();\n  // winston.debug(\"now.getTime() \" + now.getTime());\n  // winston.debug(\"this.createdAt.getTime() \" + this.createdAt.getTime());\n  // winston.debug(\"this \", this.toObject());\n  // winston.debug(\"trial \" + this.profile.trialDays * 86400000);\n  if (this.createdAt.getTime() + this.profile.trialDays * 86400000 >= now.getTime()) {\n    // winston.debug(\"not expired\");\n    return false;\n  } else {\n    // winston.debug(\"expired\");\n    return true;\n  }\n});\n\nProjectSchema.virtual('trialDaysLeft').get(function () {\n  // https://stackoverflow.com/questions/6963311/add-days-to-a-date-object\n  let now = new Date();\n  // winston.debug(\"trialDaysLeft now.getTime() \" + now.getTime());\n  // winston.debug(\"trialDaysLeft this.createdAt.getTime() \" + this.createdAt.getTime());\n  // winston.debug(\"trialDaysLeft this \", this.toObject());\n  // winston.debug(\"trialDaysLeft trial \" + this.profile.trialDays * 86400000);\n\n  const millisTrialDaysLeft = now.getTime() - (this.createdAt.getTime() + this.profile.trialDays * 86400000);\n  const trialDaysLeft = Math.floor(millisTrialDaysLeft / (60 * 60 * 24 * 1000));\n\n  // winston.debug(\"trialDaysLeft now.getTime() \" + now.getTime());\n  // winston.debug(\"trialDaysLeft this.createdAt.getTime() \" + this.createdAt.getTime());\n  // winston.debug(\"trialDaysLeft \", millisTrialDaysLeft);\n  // winston.debug(\"trialDaysLeft - PROJECT NAME \" + this.name + '; CREATED at ' + this.createdAt + ' -- trialDaysLeft: ', trialDaysLeft);\n  return trialDaysLeft\n  // return -8\n\n});\n\nProjectSchema.virtual('isActiveSubscription').get(function () {\n\n  let now = new Date();\n  // winston.debug(\"isActiveSubscription - now.getTime() \" + now.getTime());\n\n  // winston.debug(\"isActiveSubscription  - PROJECT NAME: \" + this.name);\n  // winston.debug(\"isActiveSubscription  - PROJECT profile \" + this.profile);\n  // winston.debug(\"isActiveSubscription  - PROJECT profile trialDays: \" + this.profile.trialDays);\n  // winston.debug(\"isActiveSubscription  - PROJECT profile name: \" + this.profile.name);\n  // winston.debug(\"isActiveSubscription  - PROJECT profile type: \" + this.profile.type);\n  // winston.debug(\"isActiveSubscription  - PROJECT profile subscription end date: \" + this.profile.subEnd);\n  // winston.debug(\"isActiveSubscription  -  this.activeOperatingHours: \" + this.activeOperatingHours);\n  var isActiveSubscription = '';\n  if (this.profile && this.profile.type === 'payment') {\n\n    if (this.profile.subEnd) {\n      // winston.debug(\"isActiveSubscription  - PROJECT profile subscription end date getTime(): \" + this.profile.subEnd.getTime());\n\n      var subEndPlus3gg = this.profile.subEnd.getTime() + 259200000\n      // winston.debug(\"isActiveSubscription  - PROJECT profile subscription end date getTime() + 3gg: \" + subEndPlus3gg);\n\n      // FOR DEBUG \n      var subEndMinus3gg = this.profile.subEnd.getTime() - 259200000\n      // winston.debug(\"isActiveSubscription  - PROJECT profile subscription end date getTime() - 3gg: \" + subEndMinus3gg);\n\n      // + 259200000 \n      if (now.getTime() > (this.profile.subEnd.getTime() + 259200000)) {\n        isActiveSubscription = false;\n      } else {\n        isActiveSubscription = true;\n      }\n    }\n  } else {\n    isActiveSubscription = false;\n  }\n\n  // winston.debug(\"isActiveSubscription  - isActiveSubscription \" + isActiveSubscription);\n\n  return isActiveSubscription\n\n});\n\nProjectSchema.index({ _id: 1, status: 1 }); // schema level\n\nmodule.exports = mongoose.model('project', ProjectSchema);\n"
  },
  {
    "path": "models/project_user.js",
    "content": "'use strict';\n\nvar mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar RoleConstants = require('./roleConstants');\nvar PresenceSchema = require('./presence');\nvar TagSchema = require(\"../models/tag\");\n\n\n\n  var Project_userSchema = new Schema({\n    id_project: {\n      type: Schema.Types.ObjectId,\n      ref: 'project',\n      index: true\n      // required: true\n    },\n    id_user: {      \n      type: Schema.Types.ObjectId,\n      ref: 'user',\n      index: true\n    },\n    uuid_user: {\n      type: String,\n      index: true\n      // required: true\n    },\n    role: {\n      type: String,\n      index: true\n      // required: true\n    },\n    roleType: {\n      type: Number,  //1 for agents 2 for users\n      index: true\n    },\n    user_available: {\n      type: Boolean,\n      default: true, \n      index: true\n      // required: true\n    },\n    profileStatus: {\n      type: String,\n    },\n    presence: PresenceSchema,\n    attributes: {\n      type: Object,\n    },\n    max_assigned_chat: {\n      type: Number,\n    },\n    number_assigned_requests: {\n      type: Number,\n      default:0,\n      index: true\n    },\n    last_ip: {\n        type: String,\n    },\n    last_login_at: {\n      type: Date,\n      default: new Date(),\n    },\n    settings: {\n      type: Object,\n    },\n    tags: [TagSchema],\n    permissions: [String],\n    createdBy: {\n      type: String,\n      required: true\n    },\n    status: {\n      type: String,\n      enum: ['active', 'disabled'],\n      default: \"active\",\n      index: true,\n      required: true\n    },\n    trashed: {\n      type: Boolean,\n      default: false,\n      required: false\n    }\n  }, {\n      timestamps: true,\n      toJSON: { virtuals: true } //used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html\n    }\n  );\n\n \n\n  //TODO COMMENT IT unused. Now events are not saved to the db\n// Project_userSchema.virtual('events', {\n//     ref: 'event', // The model to use\n//     localField: '_id', // Find people where `localField`\n//     foreignField: 'project_user', // is equal to `foreignField`\n//     justOne: false,\n//     // options: { getters: true }\n//     options: { sort: { createdAt: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n// });\n\nProject_userSchema.virtual('isAuthenticated').get(function () {\n  if (this.role === RoleConstants.GUEST ) {\n    return false;\n  }else {\n    return true;\n  }\n});\n\nProject_userSchema.methods.getAllPermissions = function () {\n  // console.log(\"this\", this);\n  winston.debug(\"this.permissions\", this.permissions);\n\n  // console.log(\"this.rolePermissions\", this.rolePermissions);\n  winston.debug(\"this._doc.rolePermissions\", this._doc.rolePermissions);\n\n  let all = this.permissions;\n\n  if (this._doc.rolePermissions) {\n    all = [...new Set([...this.permissions, ...this._doc.rolePermissions])]; //https://medium.com/@rivoltafilippo/javascript-merge-arrays-without-duplicates-3fbd8f4881be\n  }\n  // const all = this.permissions.concat(this._doc.rolePermissions);\n  winston.verbose(\"getAllPermissions all\", all);\n\n  return all;\n  \n}\n\n\n\nProject_userSchema.methods.hasPermissionOrRole = function (permission, roles) {\n  var all_permissions = this.getAllPermissions();\n  // var all_permissions = this.getAllPermissions();\n  // console.log(\"hasPermissionOrRole\", all_permissions, permission,  this.role, roles)\n  if (all_permissions && all_permissions.length>0 ) {\n    if (all_permissions.includes(permission)) {\n      // console.log(\"hasPermissionOrRole found\", permission)\n      return true;\n    } else {\n      return false;\n    }\n  }else {\n    if (roles instanceof Array) {\n      // console.log(\"hasPermissionOrRole roles instanceof Array\");\n      for (var i = 0; i < roles.length; i++) {\n        if (roles[i]==this.role) {\n          return true;\n        }\n      }\n      return false;\n\n    } else {\n      // console.log(\"roles instanceof  \", roles instanceof );\n      // console.log(\"this.role instanceof  \", this.role instanceof );\n      \n      // console.log(\"hasPermissionOrRole role \", this.role, roles);\n      if (this.role==roles) {\n        // console.log(\"role ok\");\n        return true;\n      } else {\n        // console.log(\"role ko\");\n        return false;\n      }\n    }\n    \n    \n  }\n}\n\nProject_userSchema.methods.hasPermission = function (permission) {\n  if (this.permissions && this.permissions.length>0 ) {\n    if (this.permissions.includes(permission)) {\n      return true;\n    } else {\n      return false;\n    }\n  }else {\n    return false;\n  }\n}\n\n\n  // var query = { id_project: req.params.projectid, id_user: req.user._id};\n  // if (req.user.sub && (req.user.sub==\"userexternal\" || req.user.sub==\"guest\")) {\n  //   query = { id_project: req.params.projectid, uuid_user: req.user._id};\n  // }\n  Project_userSchema.index({ id_project: 1, id_user: 1 }); \n  Project_userSchema.index({ id_project: 1, uuid_user: 1 }); \n  \n  // Project_user.find({ id_project: projectid, role: { $in : role }\n  Project_userSchema.index({ id_project: 1, role: 1 }); \n  // Project_user.find({ id_project: projectid, id_user: { $in : group[0].members}, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.AGENT]} })\n  Project_userSchema.index({ id_project: 1, id_user:1, role: 1 }); \n\n  // suggested by atlas\n  Project_userSchema.index({ id_project: 1, role: 1, status: 1, createdAt: 1  }); \n\n\n  Project_userSchema.pre('findOneAndUpdate', async function(next) {\n    // Get the update object\n    const update = this.getUpdate();\n\n    // Check if 'trashed' is being set to true\n    if (update && (\n        (update.trashed === true) ||\n        (update.$set && update.$set.trashed === true)\n      )) {\n      // Set status to \"disabled\"\n      if (!update.$set) {\n        update.$set = {};\n      }\n      update.$set.status = \"disabled\";\n      this.setUpdate(update);\n    }\n    next();\n  });\n\n\n  module.exports = mongoose.model('project_user', Project_userSchema);;\n\n"
  },
  {
    "path": "models/projectf.js",
    "content": "var fs = require('fs');\n\nvar util = require('util');\nvar mongoose = require('mongoose');\n\n\nconst methods = [ 'add',\n    'aggregate',\n    'count',\n    'countDocuments',\n    'create',\n    'distinct',\n    'ensureIndexes',\n    'exec',\n    'exec',\n    // 'find',\n    // 'findById',\n    'findByIdAndRemove',\n    'findByIdAndUpdate',\n    // 'findOne',\n    'findOneAndRemove',\n    'findOneAndUpdate',\n    'geoNear',\n    'geoSearch',\n    'index',\n    'insertMany',\n    'lean',\n    'lean',\n    'limit',\n    'mapReduce',\n    'plugin',\n    'populate',\n    'remove',\n    'select',\n    'set',\n    'sort',\n    'toObject',\n    'toJSON',\n    'toString',\n    'update',\n    'updateMany',\n    'where'\n];\nvar FILE = '/Users/andrealeo/dev/chat21/project.json';\n\nclass Project {\n\n\n   constructor(project) {\n\n    this._id = new mongoose.Types.ObjectId(),\n\n    this.id =  this._id;\n    this.name = project && project.name || \"default\";\n    this.activeOperatingHours = project && project.activeOperatingHours || false;\n    this.operatingHours = project && project.operatingHours || {};\n    this.settings = project && project.settings || {};\n    this.widget = project && project.widget || {};\n    this.status = project && project.status || 100;\n    this.jwtSecret = project && project.jwtSecret || \"secretjwt\";\n    this.profile = project && project.profile || {}; //TODO?\n    this.versions = project && project.versions || 200;\n    this.channels = project && project.channels ||  {\"name\": \"chat21\"};\n    this.createdBy = project && project.createdBy || \"system\";\n    this.createdOn =  project && project.createdOn ||  new Date();\n    this.updatedOn =  project && project.updatedOn || new Date();\n\n\n//   profile: {\n//     type: Profile.schema,\n//     default: function () {\n//       return new Profile();\n//     }\n//   },\n\n\n    methods.forEach(method => {\n\n      this[method] = function() {\n          return this;\n      };\n  \n     });\n  }\n\n\n  save(c) {\n    fs.writeFileSync(FILE, JSON.stringify(this)  , 'utf-8'); \n    // fs.writeFileSync('./project.json', util.inspect(obj) , 'utf-8'); \n    // return this.findOne(undefined,c);\n    return Project.cll(this,c);\n  }\n\n  static findOne(q,c) {\n    var data  = JSON.parse(fs.readFileSync(FILE, 'utf8'));\n    // console.log(\"data\", data);\n\n    // if (data.name) {\n      this.name = data.name;\n    // }\n    \n    this.activeOperatingHours = data.activeOperatingHours;\n    this.operatingHours = data.operatingHours;\n    this.settings = data.settings;\n    \n    return this.cll(q,c);\n  }\n  static cll(q, c) {\n    c(undefined, this);\n    return this;\n  }\n\n\n}\n\n\n// var project = new Project();\n// // console.log(\"project\",project);\n\n// project.save({\"name\": \"name1\"},function(err, data) {\n//   console.log(\"saved data\",data, err);\n\n//   var a = project.findOne({},function(err, data) {\n//     console.log(\"read data\",data, err);\n//   });\n  // console.log(\"a\",a);\n\n// });\n\n\n\n\n// var MongooseModel = require('../utils/mongooseModel');\n// console.log(\"MongooseModel\",MongooseModel);\n\n// var a = MongooseModel.findOne({ name: 'whiskers' }, (err, doc) => {\n//   console.log(\"doc\",doc);\n// });\n// console.log(\"a\",a);\n\n\n// var mM=mongooseModel(project);\nmodule.exports = Project;\n"
  },
  {
    "path": "models/property.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar PropertySchema = new Schema({\n  \n  label: {\n    type: String,\n    required: true,\n  },  \n  name: {\n    type: String,\n    required: true,\n    index: true\n  },  \n  type: {\n    type: String,\n    required: true,\n    default: \"text\",\n    index: true\n  },  \n  status: {\n    type: Number,\n    required: true,\n    default: 100, \n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  }\n  },{\n    timestamps: true\n  }\n);\n\nvar property = mongoose.model('property', PropertySchema);\n\n if (process.env.MONGOOSE_SYNCINDEX) {\n  property.syncIndexes();\n  winston.verbose(\"property syncIndexes\")\n}\n\nmodule.exports = property;\n"
  },
  {
    "path": "models/request.js",
    "content": "var mongoose = require('mongoose');\n// mongoose.set(\"debug\", true);\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar Channel = require('../models/channel');\nvar winston = require('../config/winston');\nvar RequestConstants = require(\"../models/requestConstants\");\nvar ChannelConstants = require(\"../models/channelConstants\");\n\nvar ProjectUserSchema = require(\"../models/project_user\").schema;\nvar RequestStatus = require(\"../models/requestStatus\");\nvar LeadSchema = require(\"../models/lead\").schema; //it's not used but i you run test like (mocha departmentService.js) it throws this not blocking exception: error: error getting requestSchema hasn't been registered for model \"lead\".\nvar NoteSchema = require(\"../models/note\").schema;\n\nvar TagSchema = require(\"../models/tag\");\nvar LocationSchema = require(\"../models/location\");\nvar RequestSnapshotSchema = require(\"../models/requestSnapshot\");\nvar ContactSchema = require(\"../models/contact\");\nvar defaultFullTextLanguage = process.env.DEFAULT_FULLTEXT_INDEX_LANGUAGE || \"none\";\nwinston.info(\"Request defaultFullTextLanguage: \"+ defaultFullTextLanguage);\n\nconst disableTicketIdSequence = process.env.DISABLE_TICKET_ID_SEQUENCE || false;\nwinston.info(\"Request disableTicketIdSequence: \"+ disableTicketIdSequence);\n\n// var autoIncrement = require('mongoose-auto-increment');\n\n//https://github.com/Automattic/mongoose/issues/5924\nmongoose.plugin(schema => { schema.options.usePushEach = true });\n\nconst AutoIncrement = require('mongoose-sequence')(mongoose);\n\n\n\nvar RequestSchema = new Schema({\n\n  // find duplicate\n  /*\n  db.getCollection('requests').aggregate( {\"$group\" : { \"_id\": \"$request_id\", \"count\": { \"$sum\": 1 } } },\n    {\"$match\": {\"_id\" :{ \"$ne\" : null } , \"count\" : {\"$gt\": 1} } }, \n    {\"$sort\": {\"count\" : -1} },\n    {\"$project\": {\"request_id\" : \"$_id\", \"_id\" : 0} })\n*/\n\n  request_id: {\n    type: String,\n    required: true,\n    index: true,\n    unique: true        \n\n  },\n\n  // TODO attiva solo condizionalmente per performance\n  ticket_id: {\n    type: Number,\n    // required: true,\n    index: true\n  },\n// The user who is asking for support through a ticket is the requester. For most businesses that use Tiledesk Support, the requester is a customer, but requesters can also be agents in your Tiledesk Support instance.  requester: {\n  requester: {\n    type: Schema.Types.ObjectId,\n    ref: 'project_user',\n    required: false, //ENABLEIT,\n    // index: true //unused\n  },\n\n\n  // ==== REQUESTER_ID====\n\n  lead: {\n    type: Schema.Types.ObjectId,\n    ref: 'lead',\n    required: false\n  },\n  // requester_id: { //rename to \"lead\"  field of type objectid and update mongodb column and data \n  //   type: String,\n  //   required: true,\n  //   index: true,\n  //   // get: v => {\n  //   //   if (ObjectId.isValid(v)){\n  //   //     console.log(\"ciaoooo ok\");\n  //   //     return v;\n  //   //   }else {\n  //   //     console.log(\"ciaoooo ko\");\n  //   //     return null;\n  //   //   }\n       \n  //   // }\n\n\n  //   // type: Schema.Types.ObjectId,\n  //   // ref: 'lead'\n  // },\n\n  subject: {\n    type: String\n  },\n  first_text: {\n    type: String,\n    required: true\n  },\n  status: {\n    type: Number,\n    required: false,\n    default: RequestConstants.UNASSIGNED,\n    // index: true //unused\n  }, \n\n\n  preflight: {\n    type: Boolean,\n    default: false,\n    index:true\n  },\n\n  hasBot: {\n    type: Boolean,\n    default: false,\n    index:true\n  },\n\n  // The admin the conversation is currently assigned to.\n// Note nobody_admin indicates the conversation is assigned to Nobody.\n  assignee: {\n    type: Schema.Types.ObjectId,\n    ref: 'project_user'\n  },\n\n  participants: {  //TODO trasformare in objectid di tipo project_user\n    type: Array,\n    required: false,\n    index: true,\n  },\n  \n  priority: {\n    type: String,\n    index: true,\n    default: \"medium\" //translate on client side\n  },\n\n  followers: [{ \n    type: Schema.Types.ObjectId, \n    ref: 'project_user' }],\n\n  participantsAgents: {  \n    type: Array,\n    required: false,\n    // index: true, //i think unused\n  },\n  participantsBots: {  \n    type: Array,\n    required: false,\n    // index: true, //i think unused\n  },\n  department: {\n    type: Schema.Types.ObjectId,\n    ref: 'department',\n    index: true\n    // required: true\n  },\n\n  transcript: {\n    type: String\n  },\n\n  //timestamp when the agent reply the first time to a visitor\n  // First reply time is the time between ticket creation and the first public comment from an agent, displayed in minutes. Some qualifications include:\n  first_response_at: {\n    type: Date,\n    // index: true // unused\n  },\n\n  //timestamp when the agent reply the first time to a visitor\n  assigned_at: {\n    type: Date,\n    // index: true //unused\n  },\n\n  // Wait Time (Average and Longest): The average and longest times visitors have been waiting for their chats to be served.\n  // Wait time is calculated as duration between the first visitor message in the chat and the first agent message. Wait time will be 0 for agent initiated or trigger initiated chats.\n  waiting_time: {\n    type: Number,\n    // index: true // why?\n  },\n\n\n\n  //Integer The number of conversation parts in this conversation.\n\n  \n  // messages_count: {\n  //   type: Number,\n  //   default: 0\n  // },\n\n  closed_at: { \n    type: Date\n  },\n  closed_by: { \n    type: String\n  },\n  duration: {\n    type: Number\n  },\n\n  tags: [TagSchema],\n\n  notes: [NoteSchema],\n\n  rating: {\n    type: Number,\n    required: false,\n  },\n  rating_message: {\n    type: String,\n    required: false,\n  }, \n  snapshot: {\n    type: RequestSnapshotSchema,\n    select: true,\n    //index: false,\n    // includeIndices: false,\n    excludeIndexes: true //testa bene\n\n    // select: false\n  }, \n\n\n  // all the agents of the project or the department at the request creation time \n  // TODO renameit\n  /*\n  agents:  {\n    type: [ProjectUserSchema],\n    select: true\n  },\n  */\n  // TODO select false???  ma serve alla dashboard\n\n  sourcePage: {\n    type: String,\n    required: false\n  },\n  language: {\n    type: String,\n    required: false\n  },\n  userAgent: {\n    type: String,\n    required: false\n  },\n\n\n  \n  channel: { //inbound. Channel used by the requester(visitor or user) to communicate with Tiledesk. If widget-> channel is chat21, if Whatsapp-> channel is Whatsapp, etc..\n    type: Channel.schema,\n    default: function() {\n      return new Channel({name: 'chat21'});\n    }\n  },\n  channelOutbound: { //outbound. Channel used to send the messages generated by the requester and internally (internal bot or info messages)  to the agents. This is chat21. Fare diagramma come foglio\n    // CANCELLA: Channel used by the messages generated by the Tiledesk(internal bots or info messages)to communicate to the requester. This is chat21\n    type: Channel.schema,\n    default: function() {\n      return new Channel({name: 'chat21'});\n    }\n  },\n\n  attributes: {\n    type: Object,\n    required: false\n  },\n  location: LocationSchema,\n  auto_close: {\n    type: Number,\n    index: true\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  smartAssignment: {\n    type: Boolean,\n    default: function() {\n      // winston.info(\"smartAssignment default this.channel.name:\" + this.channel.name);\n      if (this.channel && (this.channel.name===ChannelConstants.FORM || this.channel.name===ChannelConstants.EMAIL )) {\n        // winston.info(\"smartAssignment default return false\");\n        return false;\n      }else {\n        // winston.info(\"smartAssignment default return true\");\n        return true;\n      }\n    },\n    index: true\n  },\n  workingStatus: { //new, pending, open\n    type: String,\n    required: false,\n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  draft: {\n    type: Boolean,\n    required: false,\n    index: true\n  },\n  contact: ContactSchema,\n}, {\n  timestamps: true,\n  toObject: { virtuals: true }, //IMPORTANT FOR trigger used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html\n  toJSON: { virtuals: true } //used to polulate messages in toJSON// https://mongoosejs.com/docs/populate.html\n}\n\n);\n\nif (!disableTicketIdSequence) {\n  winston.info(\"AutoIncrement plugin enabled\");\n  RequestSchema.plugin(AutoIncrement, {id: 'ticket_seq', inc_field: 'ticket_id', reference_fields: ['id_project'], disable_hooks:false });\n}\n\n\n\n// Backcompatibility\nRequestSchema.virtual('requester_id').get(function () {\n  if (this.lead) {\n    return this.lead._id;\n  }else {\n    return null;\n  }\n});\n\n\nRequestSchema.virtual('participatingAgents', {\n  ref: 'user', // The model to use\n  localField: 'participantsAgents',\n  foreignField: '_id', // is equal to `foreignField`\n  justOne: false,\n  //options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n});\n\n\n\n\n// // work but no multiple where on id-project\n// RequestSchema.virtual('messages', {\n//   ref: 'message', // The model to use\n//   localField: 'request_id', // Find people where `localField`\n//   foreignField: 'recipient', // is equal to `foreignField`\n//   // localField: ['request_id', 'id_project'], // Find people where `localField`\n//   // foreignField: ['recipient', 'id_project'], // is equal to `foreignField`\n//   // If `justOne` is true, 'messages' will be a single doc as opposed to\n//   // an array. `justOne` is false by default.\n//   justOne: false,\n//   //options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n// });\n\n\n\n // TODO serve????? Nico dice di no. io lo uso solo per trigger fai una cosa + semplice ese hasAvailableAgent = true o false\n /* CAMBIA TRIGGER PRIMA DI PUBBLICARE\n RequestSchema.virtual('availableAgentsCount').get(function () {\n  // if (this.agents && this.agents.length>0  // I uncomment  winston.debug(\"project_user\", project_user); of the requestNotification.js row 252 this.agents doesn't have .filter method??\n  //   // &&  this.agents.filter\n  //   ) {\n\n    var project_users_available = this.snapshot.agents.filter(function (projectUser) {\n      // var project_users_available = this.agents.filter(function (projectUser) {\n      if (projectUser.user_available == true) {\n        return true;\n      }\n    });\n    // ATTENTION DO NOT PRINT INTO A VIRTUAL \n    // winston.debug('++ AVAILABLE PROJECT USERS count ', project_users_available)\n\n    // if (project_users_available && project_users_available.length>0){\n      return project_users_available.length;\n    // }else {\n    //   return 0;\n    // }\n  // } else {\n  //   return 0;  \n  // }\n\n});\n*/\n\n// RequestSchema.virtual('availableAgents').get(function () {\n//     var project_users_available = this.agents.filter(function (projectUser) {\n//       if (projectUser.user_available == true) {\n//         return true;\n//       }\n//     });\n//     winston.debug('++ AVAILABLE PROJECT USERS ', project_users_available)\n\n//     if (project_users_available && project_users_available.length>0){\n//       return project_users_available;\n//     }else {\n//       return [];\n//     }\n\n// });\n\n\nRequestSchema.virtual('participatingBots', {\n  ref: 'faq_kb', // The model to use\n  localField: \"participantsBots\",  \n  foreignField: '_id', // is equal to `foreignField`\n  justOne: false,\n  //options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options\n});\n\n\nRequestSchema.method(\"getBotId\", function () {\n      \n      \n  if ( this.participants == null) {\n      return null;\n  }\n\n  var participants = this.participants;\n  winston.debug(\"participants\", participants);\n\n  var botIdTmp;\n  \n  if (participants) {\n    participants.forEach(function(participant) { \n      //winston.debug(\"participant\", participant);\n      // bot_HERE\n      // botprefix\n      if (participant.indexOf(\"bot_\")> -1) {\n        // botprefix\n        botIdTmp = participant.replace(\"bot_\",\"\");\n        //winston.debug(\"botIdTmp\", botIdTmp);\n        //break;        \n      }\n    });\n  \n    return botIdTmp;\n  }else {\n    return null;\n  }\n\n});\n\n// https://docs.mongodb.com/manual/indexes/\n// For a single-field index and sort operations, the sort order (i.e. ascending or descending) of the index key does not matter because MongoDB can traverse the index in either direction.\nRequestSchema.index({ createdAt: -1 }); // schema level\nRequestSchema.index({ updatedAt: -1 }); // schema level\nRequestSchema.index({ id_project: 1 }); // schema level\n// https://stackoverflow.com/questions/27179664/error-when-using-a-text-index-on-mongodb/27180032\nRequestSchema.index({ id_project: 1, request_id: 1 }\n  // , { unique: true }\n  ); // query for websocket\n\n// https://docs.mongodb.com/manual/core/index-text/\n// https://docs.mongodb.com/manual/tutorial/specify-language-for-text-index/\n// https://docs.mongodb.com/manual/reference/text-search-languages/#text-search-languages\n\n//TODO cambiare dummy con language? attento che il codice deve essere compatibile\n\n\nRequestSchema.index({id_project: 1, transcript: 'text', rating_message: 'text', subject: 'text', \"tags.tag\": 'text', \"notes.text\": 'text', \"snapshot.lead.email\": 'text', \"snapshot.lead.fullname\": 'text' },  \n {\"name\":\"request_fulltext\",\"default_language\": defaultFullTextLanguage,\"language_override\": \"dummy\"}); // schema level\n\n//  let query = {id_project: operatorSelectedEvent.id_project, participants: { $exists: true, $ne: [] }};\nRequestSchema.index({ id_project: 1, participants: 1}); \n\n//  https://docs.mongodb.com/manual/core/index-compound/ The order of the fields listed in a compound index is important. The index will contain references to documents sorted first by the values of the item field and, within each value of the item field, sorted by values of the stock field. See Sort Order for more information\nRequestSchema.index({ id_project: 1, status: 1, updatedAt: -1 }); // query for websocket\nRequestSchema.index({ id_project: 1, status: 1, preflight:1, updatedAt: -1 }); // query for websocket\nRequestSchema.index({ id_project: 1, preflight:1, updatedAt: -1 }); // used query ws (topic.endsWith('/requests'))\n\nRequestSchema.index({ hasBot: 1, createdAt: 1 }); // suggested by atlas\n\n\n// suggested by atlas\nRequestSchema.index({ lead: 1, id_project: 1, participants: 1, preflight: 1, createdAt: -1 });\n// suggested by atlas\nRequestSchema.index({ lead: 1, id_project: 1, preflight: 1, createdAt: -1 });\n\n// suggested by atlas\nRequestSchema.index({ lead: 1, \"snapshot.agents.id_user\": 1, id_project: 1, preflight: 1, createdAt: -1 });\n\n\n// suggested by atlas\nRequestSchema.index({ id_project: 1, ticket_id: 1 });\n\n// suggested by atlas\nRequestSchema.index({ id_project: 1, createdAt: 1, preflight: 1});\n\n//suggested by atlas profiler. Used by auto closing requests\nRequestSchema.index({ hasBot: 1, status: 1, createdAt: 1});\n\n// suggested by atlas\nRequestSchema.index({ \"channel.name\": 1, id_project: 1, preflight: 1, \"snapshot.requester.uuid_user\": 1, createdAt: - 1, status: 1 })\nRequestSchema.index({ id_project: 1, preflight: 1, \"snapshot.agents.id_user\": 1, updatedAt: -1, draft: 1, status: 1 })\nRequestSchema.index({ id_project: 1, participants: 1, preflight: 1, updatedAt: -1, draft: 1, status: 1 })\nRequestSchema.index({ id_project: 1, preflight: 1, updatedAt: -1, draft: 1, status: 1 })\nRequestSchema.index({ id_project: 1, preflight: 1, \"snapshot.requester.uuid_user\": 1, createdAt: -1, status: 1 })\nRequestSchema.index({ department: 1, id_project: 1, participants: 1, preflight: 1, createdAt: -1, status: 1 })\nRequestSchema.index({ id_project: 1, preflight: 1, createdAt: -1, status: 1 });\nRequestSchema.index({ id_project: 1, preflight: 1, createdAt: 1 })\nRequestSchema.index({ participants: 1, id_project: 1, createdAt: -1, status: 1 })\nRequestSchema.index({ id_project: 1, \"snapshot.lead.email\": 1, createdAt: -1, status: 1 })\nRequestSchema.index({ id_project: 1, createdAt: -1, status: 1 })\nRequestSchema.index({ id_project: 1, preflight: 1, smartAssignment: 1, \"snapshot.department.routing\": 1, createdAt: 1, status: 1 })\n\nRequestSchema.index({ status: 1, hasBot: 1, updatedAt: 1 }) // For closing unresponsive requests\nRequestSchema.index({ status: 1, hasBot: 1, workingStatus: 1, updatedAt: 1 }) // For closing unresponsive requests\n\n// Contact search by phone / email\nRequestSchema.index({ id_project: 1, 'contact.phone': 1 });\nRequestSchema.index({ id_project: 1, 'contact.email': 1 });\n\n// ERROR DURING DEPLOY OF 2.10.27\n//RequestSchema.index({ id_project: 1, participants: 1, \"snapshot.agents.id_user\": 1, createdAt: -1, status: 1 })\n\n//   cannot index parallel arrays [agents] [participants] {\"driv\n// RequestSchema.index({ id_project: 1, status: 1, preflight:1, participants:1, \"agents.id_user\":1, updatedAt: -1 }); //NN LO APPLICA\n \n// RequestSchema.index({ id_project: 1, status: 1, preflight:1, agents.id_user:1, updatedAt: -1 }); // query for websocket\n// https://docs.mongodb.com/manual/core/index-multikey/#compound-multikey-indexes You cannot create a compound multikey index if more than one to-be-indexed field of a document is an array. For example, consider a collection that contains the following document:\n\n\n// Attention. https://docs.mongodb.com/manual/core/index-compound/ If you have a collection that has both a compound index and an index on its prefix (e.g. { a: 1, b: 1 } and { a: 1 }), if neither index has a sparse or unique constraint, then you can remove the index on the prefix (e.g. { a: 1 }). MongoDB will use the compound index in all of the situations that it would have used the prefix index.\n\n\nvar request =  mongoose.model('request', RequestSchema);\nif (process.env.MONGOOSE_SYNCINDEX) {\n  request.syncIndexes();\n  winston.verbose(\"message syncIndexes\")\n\n}\n\nmodule.exports =request\n"
  },
  {
    "path": "models/requestConstants.js",
    "content": "module.exports = {\n        TEMP : 50,\n        UNASSIGNED : 100,\n        ABANDONED: 150,\n        ASSIGNED : 200,\n        SERVED : 300,\n        CLOSED: 1000\n}\n"
  },
  {
    "path": "models/requestSnapshot.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar ProjectUserSchema = require(\"../models/project_user\").schema;\nvar DepartmentSchema = require(\"../models/department\").schema;\nvar LeadSchema = require(\"../models/lead\").schema;\n\nvar RequestSnapshotSchema = new Schema({\n  \n  requester: {\n    type: ProjectUserSchema,  \n  },\n  lead: {\n    type: LeadSchema,    \n  },\n  department: {\n    type: DepartmentSchema,       \n  },\n  agents:  {\n    type: [ProjectUserSchema],\n    // select: true\n    select: false\n  },\n  // participatingAgents\n  availableAgentsCount: {\n    type: Number,\n    index:true\n  }\n  // participatingBots\n},{ _id : false }\n);\n\n\nmodule.exports = RequestSnapshotSchema;\n"
  },
  {
    "path": "models/requestStatus.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar RequestStatusSchema = new Schema({ \n  closed : {\n    type: Boolean,\n    required: true,\n    default: false,\n    index: true\n  },\n  preflight : {\n    type: Boolean,\n    required: true,\n    default: false,\n    index: true\n  }\n});\n\n\nmodule.exports = RequestStatusSchema;\n"
  },
  {
    "path": "models/requester.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar RequesterSchema = new Schema({\n  ref: {\n    type: Schema.Types.ObjectId,\n    // ref: 'department',\n    // required: true\n  },\n  type : {\n    type: String,\n    // required: true\n  }\n});\nvar requester = mongoose.model('requester', RequesterSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  requester.syncIndexes();\n  winston.verbose(\"requester syncIndexes\")\n}\n\nmodule.exports = requester;\n"
  },
  {
    "path": "models/role.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\nvar RoleSchema = new Schema({\n  name: { \n    type: String,\n    required: true,\n    index:true\n  },\n  permissions: [String],\n//   permissions: {\n//     type: String,\n//     required: true\n// },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n\n});\nvar role = mongoose.model('role', RoleSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  role.syncIndexes();\n  winston.verbose(\"role syncIndexes\")\n}\n\nmodule.exports = role;\n"
  },
  {
    "path": "models/roleConstants.js",
    "content": "module.exports = {\n        TYPE_AGENTS : 1,\n        TYPE_USERS : 2,\n        // SUPERADMIN : 'superadmin',\n        OWNER : 'owner',\n        ADMIN : 'admin',\n        SUPERVISOR : 'supervisor',\n        AGENT : 'agent',       \n        TEAMMATE : 'teammate',\n        USER : 'user',            \n        GUEST : 'guest',            \n}\n"
  },
  {
    "path": "models/routerLogger.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar config = require('../config/database');\n\n\nvar winston = require('../config/winston');\n\nvar dbUrl = process.env.MONGODB_LOGS_URI || config.databaselogs || process.env.MONGODB_URI;\nwinston.info('VisitorCounterSchema dbUrl: '+dbUrl);\n\n// mongoose.set('useFindAndModify', false); //??\n// mongoose.set('useCreateIndex', true);\n// mongoose.set('useUnifiedTopology', true); \n\nvar conn      = mongoose.createConnection(dbUrl, { \"autoIndex\": true });\t\n// var conn      = mongoose.connect(dbUrl, { \"useNewUrlParser\": true, \"autoIndex\": true });\n\n// db.getCollection('reqlogs').aggregate([ {$group:{_id:{id_project:\"$id_project\"},  \"count\":{$sum:1}}},{$sort:{\"count\":-1}}])\n// db.getCollection('projects').find({\"_id\":ObjectId(\"5afeaf94404bff0014098f54\")})\n\nvar RouterLoggerSchema = new Schema({\n  url: {\n    type: String,\n    index: true \n  },\n  fullurl: {\n    type: String,\n    index: true \n  },\n\n  // ip: {\n  //   type: String,\n  //   index: true \n  // },\n  // host: {\n  //   type: String,\n  //   index: true \n  // },\n  origin: {\n    type: String,\n    index: true \n  },\n  id_project: {\n    type: String,\n    index: true\n    //required: true\n  }\n}, {\n    timestamps: true\n  }\n);\n\nvar routerLogger = conn.model('router_logger', RouterLoggerSchema);\n\n\n\nmodule.exports = routerLogger;\n"
  },
  {
    "path": "models/routingConstants.js",
    "content": "module.exports = {\n        POOLED : 'pooled',\n        ASSIGNED : 'assigned',\n}\n"
  },
  {
    "path": "models/segment.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\n\n\nvar SegmentFilterSchema = new Schema({\n    field: { //ex: email\n        type: String,\n        required: true,\n        // index:true\n      },\n    operator: {\n        type: String,\n        required: true\n    },\n    value: { //tidio supports date, tag, dropdown\n        //type: String,\n        type: Object,\n        required: true\n    },\n},{ _id : false });\n\n\nvar SegmentSchema = new Schema({\n\n  name: {\n    type: String,\n    required: true,\n    // index: true\n  },\n  match: {\n    type: String,\n    required: true,\n    // index: \n    default: \"all\" //or any\n  },\n  filters: [SegmentFilterSchema],\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  status: {\n    type: Number,\n    default: 100,  \n    required: true,\n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\n\nvar segment = mongoose.model('segment', SegmentSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n    segment.syncIndexes();\n  winston.verbose(\"segment syncIndexes\")\n}\n\nmodule.exports = segment;\n"
  },
  {
    "path": "models/setting.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\nvar config = require('../config/database');\n\nvar pjson = require('../package.json');\n// console.log(pjson.version);\n\nvar SettingSchema = new Schema({\n  \n  identifier: {\n    type: String,\n    unique: true,//remove unique on db\n    required: true,\n    default: \"1\",\n    index: true\n  },  \n  databaseSchemaVersion: {\n    type: Number,\n    required: true,\n    default: config.schemaVersion\n  }, \n  installationVersion: {\n    type: String,\n    required: false,\n    default: pjson.version\n  }\n}\n,{\n  timestamps: true\n}\n);\n\n\n\nvar setting =  mongoose.model('Setting', SettingSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  setting.syncIndexes();\n  winston.verbose(\"setting syncIndexes\")\n}\n\n\nmodule.exports = setting;\n"
  },
  {
    "path": "models/subscription.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nconst uuidv4 = require('uuid/v4');\n\nvar winston = require('../config/winston');\n\nvar SubscriptionSchema = new Schema({\n  event: {\n    type: String,\n    required: true,\n    index: true\n  },\n  target: {\n    type: String,\n    required: true,\n    index: true\n  },\n  secret: {\n    type: String,\n    required: true,\n    default: uuidv4(),\n    select: false\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  global: {\n    type: Boolean,\n    default: false,\n    select: false,\n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n// Subscription.find({event:event, $or:[{id_project: id_project}, {global: true}]})\nSubscriptionSchema.index({ event: 1, id_project: 1, global: 1  }); \n\nSubscriptionSchema.index({ id_project: 1, event: 1, target: 1  }, { unique: true }); \n\nvar subscription = mongoose.model('subscription', SubscriptionSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  subscription.syncIndexes();\n  winston.verbose(\"subscription syncIndexes\")\n}\n\nmodule.exports = subscription;\n"
  },
  {
    "path": "models/subscriptionLog.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n\n\nvar SubscriptionLogSchema = new Schema({\n  event: {\n    type: String,\n    required: true\n  },\n  target: {\n    type: String,\n    required: true\n  },\n  response: {\n    type: String,\n  },\n  jsonRequest:{\n    type: String,\n  },\n  body: {\n    type: String,\n  },\n  err: {\n    type: String,\n  },\n  id_project: {\n    type: String,\n    required: true,\n     index:true\n  }\n}, {\n    timestamps: true\n  }\n);\n\nmodule.exports = mongoose.model('subscriptionLog', SubscriptionLogSchema);\n"
  },
  {
    "path": "models/tag.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../config/winston');\n\nvar TagSchema = new Schema({\n  \n  tag: {\n    type: String,\n    required: true,\n    index: true\n  },  \n  color: {\n    type: String,\n    required: false,\n    index: false\n  },  \n  attributes: {\n    type: Object,\n  },\n}\n);\n\n\nmodule.exports = TagSchema;\n"
  },
  {
    "path": "models/tagLibrary.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar TagSchema = require('./tag');\n\n\n\nvar TagLibrarySchema = new Schema({\n  ...TagSchema.obj,\n  // tag: {\n  //   type: String,\n  //   required: true,\n  //   index: true\n  // },  \n  // color: {\n  //   type: String,\n  //   required: false,\n  //   index: false\n  // },  \n  // attributes: {\n  //   type: Object,\n  // },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  }\n},{\n  timestamps: true\n}\n);\n\nTagLibrarySchema.index({ id_project: 1, tag: 1 }, { unique: true }); \n\n\n var Tag = mongoose.model('tag', TagLibrarySchema);\n\n\nmodule.exports = Tag;\n"
  },
  {
    "path": "models/transaction.js",
    "content": "const mongoose = require('mongoose');\n\nconst TransactionSchema = mongoose.Schema({\n  transaction_id: {\n    type: String,\n    required: true\n  },\n  id_project: {\n    type: String,\n    required: true\n  },\n  template_name: {\n    type: String,\n    required: true\n  },\n  status: {\n    type: String,\n    required: false\n  },\n  channel: {\n    type: String,\n    required: false\n  },\n  createdAt: {\n    type: Date,\n    default: Date.now\n  },\n  updatedAt: {\n    type: Date,\n    default: Date.now,\n  }\n})\n\n\nconst Transaction = mongoose.model(\"Transactions\", TransactionSchema);\n\nmodule.exports = { Transaction };"
  },
  {
    "path": "models/user.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar bcrypt = require('bcrypt-nodejs');\nvar winston = require('../config/winston');\n\nvar UserSchema = new Schema({\n    _id: Schema.Types.ObjectId,\n    email: {\n        type: String,\n        unique: true,//remove unique on db\n        required: true,\n        index: true\n    },\n    password: {\n        type: String,\n        required: true,\n        // https://stackoverflow.com/questions/12096262/how-to-protect-the-password-field-in-mongoose-mongodb-so-it-wont-return-in-a-qu\n        select: false\n    },\n    firstname: {\n        type: String,\n        // required: true\n    },\n    lastname: {\n        type: String,\n        // required: true\n    },\n    emailverified: {\n        type: Boolean,\n        // required: true\n    },\n    resetpswrequestid: {\n        type: String,\n        select: false\n    },\n    signedInAt: {\n        type: Date\n    },\n\n    // db.users.find({authUrl: {$exists : false }}).forEach(function(mydoc) {\n    //     db.users.update({_id: mydoc._id}, {$set: {authUrl: Math.random().toString(36).substring(2) + Date.now().toString(36)}})\n    //   })\n\n    authUrl: {\n        type: String,\n        index: true\n    },\n    attributes: {\n        type: Object,\n    },\n    status: {\n        type: Number,\n        required: true,\n        default: 100,\n        index: true,\n        // select: false\n    },\n    description: {\n        type: String,\n    },\n    public_email: {\n        type: String,\n        required: false\n    },\n    public_website: {\n        type: String,\n        required: false\n    },\n    phone: {\n        type: String,\n        required: false,\n        unique: true,\n        trim: true,\n        match: [/^\\+?[1-9]\\d{1,14}$/, 'Invalid phone number format'], // E.164 format\n    }\n    // authType: { // update db old data\n    //     type: String,\n    //     index:true,\n    //     default: 'email_password'\n    // },\n    // auth: {\n    //     type: Schema.Types.ObjectId,\n    //     ref: 'auth',\n    //     //required: true\n    //   },       \n}, {\n    timestamps: true\n}\n);\n\n// UserSchema.set('toJSON', {\n//     transform: function(doc, ret, opt) {\n//         delete ret['password']\n//         return ret\n//     }\n// });\n\nUserSchema.pre('save', function (next) {\n    var user = this;\n    if (this.isModified('password') || this.isNew) {\n        bcrypt.genSalt(10, function (err, salt) {\n            if (err) {\n                return next(err);\n            }\n            bcrypt.hash(user.password, salt, null, function (err, hash) {\n                if (err) {\n                    return next(err);\n                }\n                user.password = hash;\n                next();\n            });\n        });\n    } else {\n        return next();\n    }\n});\n\nUserSchema.methods.comparePassword = function (passw, cb) {\n    bcrypt.compare(passw, this.password, function (err, isMatch) {\n        if (err) {\n            return cb(err);\n        }\n        cb(null, isMatch);\n    });\n};\n\nUserSchema.virtual('fullName').get(function () {\n    return (this.firstname || '') + ' ' + (this.lastname || '');\n});\n\n\n//UserSchema.index({ email: 1, authType: 1 }, { unique: true }); \n\nvar UserModel = mongoose.model('user', UserSchema);\n\n\n// UserModel.getFullname = function () {\n//     return \n// };\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n    UserModel.syncIndexes();\n    winston.verbose(\"UserModel syncIndexes\")\n}\n\nmodule.exports = UserModel;\n"
  },
  {
    "path": "models/webhook.js",
    "content": "const mongoose = require('mongoose');\nconst { customAlphabet } = require('nanoid');\nconst nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 32);\n\nconst WebhookSchema = mongoose.Schema({\n  webhook_id: {\n    type: String,\n    default: () => nanoid(),\n    unique: true,\n    required: true\n  },\n  id_project: {\n    type: String,\n    required: true\n  },\n  name: {\n    type: String,\n    required: false\n  },\n  chatbot_id: {\n    type: String,\n    required: true\n  },\n  block_id: {\n    type: String,\n    required: true,\n  },\n  async: {\n    type: Boolean,\n    required: true,\n    default: true\n  },\n  copilot: {\n    type: Boolean,\n    required: false\n  },\n  enabled: {\n    type: Boolean,\n    required: true,\n    default: true\n  }\n}, {\n  timestamps: true\n})\n\n// Indexes\nWebhookSchema.index({ id_project: 1, chatbot_id: 1}, { unique: true })\nWebhookSchema.index({ id_project: 1, webhook_id: 1}, { unique: true })\n\n// Middlewares\nWebhookSchema.pre(\"save\", async function (next) {\n  if (this.isNew) {\n\n    try {\n      const chatbot = await mongoose.model(\"faq_kb\").findById(this.chatbot_id);\n      if (chatbot) {\n        this.name = chatbot.name + \"-webhook\";\n      }\n\n      next();\n\n    } catch(error) {\n      next(error);\n    }\n  }\n});\n\n\nconst Webhook = mongoose.model(\"Webhook\", WebhookSchema);\n\nif (process.env.MONGOOSE_SYNCINDEX) {\n  Webhook.syncIndexes();\n  winston.verbose(\"Webhook syncIndexes\");\n}\n\nmodule.exports = { Webhook };"
  },
  {
    "path": "models/whatsappLog.js",
    "content": "const mongoose = require('mongoose');\n\nconst MessageLogSchema = mongoose.Schema({\n  id_project: {\n    type: String,\n    required: true\n  },\n  json_message: {\n    type: Object,\n    required: true\n  },\n  transaction_id: {\n    type: String,\n    required: true\n  },\n  message_id: {\n    type: String,\n    required: false\n  },\n  status: {\n    type: String,\n    required: true\n  },\n  status_code: {\n    type: Number,\n    required: true\n  },\n  error: {\n    type: String\n  },\n  timestamp: {\n    type: Date,\n    default: Date.now\n  }\n})\n\n\nconst MessageLog = mongoose.model(\"MessageLog\", MessageLogSchema);  \n\nmodule.exports = { MessageLog };"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"@tiledesk/tiledesk-server\",\n  \"description\": \"The Tiledesk server module\",\n  \"version\": \"2.18.3\",\n  \"scripts\": {\n    \"start\": \"node ./bin/www\",\n    \"pretest\": \"mongodb-runner start\",\n    \"watchenv\": \"nodemon --watch .env --watch ./confenv/.env ./bin/www\",\n    \"test\": \"mocha ./test/*.js ./pubmodules/**/test/*.js ./modules/**/test/*.js ./channels/*/test/*.js ./node_modules/@tiledesk-ent/**/test/*.js  --exit\",\n    \"test:int\": \"mocha ./test-int/*.js  ./pubmodules/**/test-int/*.js ./modules/**/test-int/*.js ./channels/**/test-int/*.js --exit\",\n    \"posttest\": \"mongodb-runner stop\",\n    \"preinstall\": \"if [ $ENABLE_ENTERPRISE_MODULE ]; then npm run enable-ent; fi\",\n    \"heroku-prebuild\": \"if [ $ENABLE_ENTERPRISE_MODULE ]; then npm run enable-ent; fi\",\n    \"enable-ent\": \"echo //registry.npmjs.org/:_authToken=${NPM_TOKEN} > .npmrc\"\n  },\n  \"private\": false,\n  \"author\": \"Andrea Leo - Tiledesk SRL\",\n  \"license\": \"AGPL-3.0\",\n  \"homepage\": \"https://www.tiledesk.com\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/Tiledesk/tiledesk-server.git\"\n  },\n  \"engines\": {\n    \"node\": \"16.17.0\",\n    \"npm\": \"8.15.0\"\n  },\n  \"bin\": {\n    \"tiledesk-server\": \"./bin/www\"\n  },\n  \"optionalDependencies\": {\n    \"@tiledesk-ent/tiledesk-server-enterprise\": \"^1.0.0\",\n    \"@tiledesk-ent/tiledesk-server-jwthistory\": \"^1.1.9\",\n    \"@tiledesk-ent/tiledesk-server-payments\": \"^1.1.12\",\n    \"@tiledesk-ent/tiledesk-server-request-history\": \"^1.1.5\",\n    \"@tiledesk-ent/tiledesk-server-visitorcounter\": \"^1.1.1\"\n  },\n  \"dependencies\": {\n    \"@tiledesk/tiledesk-apps\": \"^1.0.28\",\n    \"@tiledesk/tiledesk-chat21-app\": \"^1.1.8\",\n    \"@tiledesk/tiledesk-chatbot-templates\": \"^0.1.3\",\n    \"@tiledesk/tiledesk-chatbot-util\": \"^0.8.33\",\n    \"@tiledesk/tiledesk-client\": \"^0.10.10\",\n    \"@tiledesk/tiledesk-dialogflow-connector\": \"^1.8.4\",\n    \"@tiledesk/tiledesk-json-rules-engine\": \"^4.0.3\",\n    \"@tiledesk/tiledesk-kaleyra-proxy\": \"^0.1.7\",\n    \"@tiledesk/tiledesk-messenger-connector\": \"^0.1.28\",\n    \"@tiledesk/tiledesk-multi-worker\": \"^0.3.3\",\n    \"@tiledesk/tiledesk-rasa-connector\": \"^1.0.10\",\n    \"@tiledesk/tiledesk-sms-connector\": \"^0.1.13\",\n    \"@tiledesk/tiledesk-telegram-connector\": \"^0.1.14\",\n    \"@tiledesk/tiledesk-tybot-connector\": \"^2.0.45\",\n    \"@tiledesk/tiledesk-voice-twilio-connector\": \"^0.3.2\",\n    \"@tiledesk/tiledesk-vxml-connector\": \"^0.1.91\",\n    \"@tiledesk/tiledesk-whatsapp-connector\": \"1.0.26\",\n    \"@tiledesk/tiledesk-whatsapp-jobworker\": \"^0.0.13\",\n    \"amqplib\": \"^0.5.5\",\n    \"app-root-path\": \"^3.0.0\",\n    \"bcrypt-nodejs\": \"0.0.3\",\n    \"body-parser\": \"^1.20.0\",\n    \"cachegoose\": \"^8.0.0\",\n    \"connect-redis\": \"^7.1.0\",\n    \"cookie-parser\": \"^1.4.6\",\n    \"cors\": \"^2.8.5\",\n    \"csv-express\": \"^1.2.2\",\n    \"debug\": \"^4.3.4\",\n    \"dompurify\": \"^3.2.6\",\n    \"dotenv\": \"^8.6.0\",\n    \"email-templates\": \"^8.1.0\",\n    \"eventemitter2\": \"^6.4.4\",\n    \"express\": \"^4.17.3\",\n    \"express-ipfilter\": \"^1.2.0\",\n    \"express-recaptcha\": \"^5.1.0\",\n    \"express-session\": \"^1.17.3\",\n    \"express-validator\": \"^6.14.2\",\n    \"fast-csv\": \"^4.3.6\",\n    \"file-type\": \"^14.7.1\",\n    \"geoip-lite\": \"^1.4.5\",\n    \"handlebars\": \"^4.7.7\",\n    \"handlebars-dateformat\": \"^1.1.3\",\n    \"html-entities\": \"^2.3.3\",\n    \"http\": \"0.0.0\",\n    \"immutable\": \"^4.1.0\",\n    \"jade\": \"~1.11.0\",\n    \"jobs-worker-queued\": \"^0.0.5\",\n    \"jsdom\": \"^26.1.0\",\n    \"jsonwebtoken\": \"^8.5.1\",\n    \"libphonenumber-js\": \"^1.12.10\",\n    \"lodash\": \"^4.17.21\",\n    \"marked\": \"^3.0.4\",\n    \"maskdata\": \"^1.1.10\",\n    \"migrate-mongoose\": \"^4.0.0\",\n    \"mix-hash\": \"^1.0.7\",\n    \"moment\": \"^2.29.3\",\n    \"moment-timezone\": \"^0.5.33\",\n    \"mongoose\": \"^5.13.14\",\n    \"mongoose-sequence\": \"^5.3.1\",\n    \"morgan\": \"^1.10.0\",\n    \"multer\": \"^1.4.4\",\n    \"multer-gridfs-storage\": \"^4.2.0\",\n    \"nanoid\": \"^3.3.4\",\n    \"nock\": \"^13.5.4\",\n    \"node-rsa\": \"^1.1.1\",\n    \"node-schedule\": \"^2.1.0\",\n    \"nodemailer\": \"^6.7.5\",\n    \"passport\": \"^0.4.1\",\n    \"passport-google-oidc\": \"^0.1.0\",\n    \"passport-http\": \"^0.3.0\",\n    \"passport-jwt\": \"^4.0.0\",\n    \"passport-oauth2\": \"^1.8.0\",\n    \"pdfmake\": \"^0.2.5\",\n    \"promise-events\": \"^0.2.4\",\n    \"request\": \"^2.88.2\",\n    \"request-promise\": \"^4.2.6\",\n    \"retry-request\": \"^4.2.2\",\n    \"serve-favicon\": \"~2.5.0\",\n    \"sharp\": \"^0.27.2\",\n    \"sitemapper\": \"^3.2.24\",\n    \"stripe\": \"^7.2.0\",\n    \"uniqid\": \"^5.4.0\",\n    \"uuid\": \"^3.3.3\",\n    \"winston\": \"^3.7.2\",\n    \"winston-mongodb\": \"^5.0.7\",\n    \"ws\": \"^7.5.8\"\n  },\n  \"devDependencies\": {\n    \"chai\": \"^4.3.6\",\n    \"chai-http\": \"^4.3.0\",\n    \"chai-string\": \"^1.5.0\",\n    \"loadtest\": \"^5.2.0\",\n    \"mocha\": \"^8.4.0\",\n    \"mongodb-runner\": \"4.8.3\",\n    \"nodemon\": \"^2.0.16\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/Tiledesk/tiledesk-server/issues\"\n  },\n  \"main\": \"./bin/www\",\n  \"directories\": {\n    \"doc\": \"docs\",\n    \"test\": \"test\"\n  },\n  \"keywords\": [\n    \"livechat\",\n    \"chatbot\",\n    \"widget\",\n    \"messenger\"\n  ]\n}\n"
  },
  {
    "path": "public/loaderio-e6d5fbf72a45848fe2f43f7a986a1103.txt",
    "content": "loaderio-e6d5fbf72a45848fe2f43f7a986a1103"
  },
  {
    "path": "public/samples/bot/external/searcher/parse",
    "content": "{\n    \"text\": \"question\",\n    \"intent\": { \"name\": \"brutteparole\", \"confidence\": 0.9257488250732422 },\n    \"entities\": [],\n    \"text_tokens\": [ [ 0, 8 ] ],\n    \"intent_ranking\": [\n      { \"name\": \"brutteparole\", \"confidence\": 0.9257488250732422 },\n      { \"name\": \"eta\", \"confidence\": 0.0742512047290802 }\n    ]\n}"
  },
  {
    "path": "public/stylesheets/style.css",
    "content": "body {\n  padding: 50px;\n  font: 14px \"Lucida Grande\", Helvetica, Arial, sans-serif;\n}\n\na {\n  color: #00B7FF;\n}\n"
  },
  {
    "path": "public/wstest/TB.js",
    "content": "// https://www.dofactory.com/javascript/singleton-design-pattern\nvar Singleton = (function () {\n    var instance;\n \n    function createInstance() {\n        var object = new Object(\"I am the instance\");\n        return object;\n    }\n \n    return {\n        getInstance: function () {\n            if (!instance) {\n                instance = createInstance();\n            }\n            return instance;\n        }\n    };\n})();"
  },
  {
    "path": "public/wstest/index.html",
    "content": "<html>\n    <head>\n            <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js\"></script>\n            <script src=\"./tilebase.js\"></script>\n\n            <!-- <script type=\"text/javascript\" src=\"./main.js\"></script> -->\n\n            <script>\n                    var ws = new WebSocket('ws://localhost:3000/?token=JWT%20eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZWU4OTE5ZDg3MjJiMzAwMzQ3ZWM1NmYiLCJlbWFpbCI6ImFkbWluQHRpbGVkZXNrLmNvbSIsImZpcnN0bmFtZSI6IkFkbWluaXN0cmF0b3IiLCJsYXN0bmFtZSI6IiAiLCJlbWFpbHZlcmlmaWVkIjp0cnVlLCJpYXQiOjE2ODM2NTEyMDYsImF1ZCI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwiaXNzIjoiaHR0cHM6Ly90aWxlZGVzay5jb20iLCJzdWIiOiJ1c2VyIiwianRpIjoiZDVlY2U3YjctN2U0Zi00NThmLTkxYWMtNGJiMzY3NDdmNmUxIn0.KiPGusVCPR0-IROsoPiH93V_vCKJS3rK_ikjqmngQT7J_s3VHalRvGfuvds-sRg4V8rfZyhlfaR6uWR71TYPhyT7REPYORp-T-6ddb8g7nL4lraHVhzqYYpVPIhXUM81rkyHm1FP2LuIsEe93Ir62AyK0xajoS1wPdjYNBxari0');                                                                                    \n                    // var ws = new WebSocket('ws://eu.rtmv3.tiledesk.com/mqws/ws/?token=JWT%20eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI1ZWI1NmUxYTFmOWUxZjAwMTJkNjIyNzUiLCJlbWFpbCI6ImxvYWRAbG9hZC5pdCIsImZpcnN0bmFtZSI6ImxvYWQiLCJsYXN0bmFtZSI6ImxvYWQiLCJlbWFpbHZlcmlmaWVkIjpmYWxzZSwiaWF0IjoxNTg4OTQ4NTA3LCJhdWQiOiJodHRwczovL3RpbGVkZXNrLmNvbSIsImlzcyI6Imh0dHBzOi8vdGlsZWRlc2suY29tIiwic3ViIjoidXNlciIsImp0aSI6IjU0OWQ3ODI4LWI5ZDAtNDAxNS1iMDQ4LTA0M2IyYzYyZjAxOSJ9.gnGJRtUoRpUA90QWNSp1wmWmlJcu1YwYf_kJ8yUi1_I');                                                                                    \n                                        ws.onopen = function () {\n                                            console.log('websocket is connected ...');\n                                            ws.send('{ \"action\": \"subscribe\", \"payload\": { \"topic\": \"/6453d246f1e784003a97537b/bots/645903253e16b1002cfe4439\"}}');\n                                            // ws.send('{ \"action\": \"subscribe\", \"payload\": { \"topic\": \"/5eb56e211f9e1f0012d6227b/requests\"}}');\n                                        }\n            </script>\n\n\n\n\n    </head>\n    <bdoy>\n            <h1>\n                    Web Socket Example\n                </h1>\n                <p>\n                    open console to see connection.\n                </p>\n                <div id=\"messages\"></div>\n                \n    </bdoy>\n</html>\n"
  },
  {
    "path": "public/wstest/tilebase.js",
    "content": "class Tilebase {\n\n\n    constructor(url, onCreate, onUpdate) {\n      this.url = url;\n      this.onCreate = onCreate;\n      this.onUpdate = onUpdate;\n      //this.data = [];\n      \n\n    }\n/*\n    insertOrUpdate(item) {\n        let objIndex = this.data.findIndex((obj => obj._id == item._id));\n        // console.log(\"objIndex\", objIndex);\n        if (objIndex==-1) {\n            this.data.push(item);\n        }else if (objIndex>-1) {\n            //console.log(\"Before objIndex: \", this.data[objIndex])\n            this.data[objIndex] = item;\n            //console.log(\"After objIndex: \", this.data[objIndex])\n        } else {\n            console.error(\"error\");\n        }\n       // console.log(\"this.data\", this.data)\n        \n        return objIndex;\n\n    }\n\n\n*/\n    send (message) {\n        console.log(\"send message\", message);\n        this.ws.send(message);\n    }\n    start(initialMessage) {\n        // start(token) {\n        var that = this;\n        return new Promise(function (resolve, reject) {\n\n            // var options = {\n            //     // headers: {\n            //     //     \"Authorization\" : \"JWT \" + token\n            //     // }\n            // };\n            // console.log('options', options);\n\n        // var ws = new WebSocket('ws://localhost:3000');\n                    // var ws = new WebSocket('ws://localhost:3000/public/requests');\n                    // var ws = new WebSocket('ws://localhost:3000/5bae41325f03b900401e39e8/messages');\n                    \n                    // 'ws://localhost:40510'\n                    that.ws = new WebSocket(that.url);\n                    var ws = that.ws;\n                    // var ws = new WebSocket(that.url, options);\n                    ws.onopen = function () {\n                        console.log('websocket is connected2 ...');\n                        if (initialMessage) {\n                            ws.send(initialMessage);\n                        }\n                       // ws.send('connected')\n                    }\n                    ws.onclose = function () {\n                        console.log('websocket is closed ... Try to reconnect in 5 seconds');\n                        // https://stackoverflow.com/questions/3780511/reconnection-of-client-when-server-reboots-in-websocket\n                        // Try to reconnect in 3 seconds\n                        setTimeout(function(){that.start()}, 3000);\n                    }\n                    ws.onerror = function () {\n                        console.log('websocket error ...')\n                    }\n                    ws.onmessage = function(message) {   \n                        console.log(message);\n\n                       \n                            try {\n                                var json = JSON.parse(message.data);\n                            } catch (e) {\n                                console.log('This doesn\\'t look like a valid JSON: ',\n                                    message.data);\n                                    return;\n                                // return reject('This doesn\\'t look like a valid JSON: ',\n                                //         message.data);\n                            }\n                            \n                            if (json && json.payload  && json.payload.message && that.isArray(json.payload.message)) {\n                                json.payload.message.forEach(element => {\n                                   // console.log(\"element\", element);\n                                    //let insUp = that.insertOrUpdate(element);\n                            let insUp = json.payload.method;\n                                console.log(\"insUp\",insUp);\n\n                                    var object = {event: json.payload, data: element};\n\n                                    if (insUp==\"CREATE\" && that.onCreate) {\n                                        that.onCreate(element, object);\n                                    }\n                                    if (insUp==\"UPDATE\" && that.onUpdate) {\n                                        that.onUpdate(element, object);\n                                    }\n                                    //this.data.push(element);\n                                    \n                                     resolve(element, object);\n                                    // $('#messages').after(element.text + '<br>');\n                                });\n                            }else {\n                                //let insUp = that.insertOrUpdate(json.payload.message);\n\t\t\t\t                let insUp = json.payload.method;                                                                                                                                                                                                                         \n                                  console.log(\"insUp\",insUp);     \n\n                                var object = {event: json.payload, data: json};\n\n                                if (insUp==\"CREATE\" && that.onCreate) {\n                                    that.onCreate(json.payload.message, object);\n                                }\n                                if (insUp==\"UPDATE\" && that.onUpdate) {\n                                    that.onUpdate(json.payload.message, object);\n                                }\n                                 resolve(json.payload.message, object);\n                                // resolve\n                                // $('#messages').after(json.text + '<br>');\n                            }\n\n                    }    \n                    \n        });\n\n    }\n\n    isArray(what) {\n        return Object.prototype.toString.call(what) === '[object Array]';\n    }\n\n  }\n"
  },
  {
    "path": "publiccode.yml",
    "content": "# This repository adheres to the publiccode.yml standard by including this \n# metadata file that makes public software easily discoverable.\n# More info at https://github.com/italia/publiccode.yml\n\npubliccodeYmlVersion: '0.2'\ncategories:\n  - customer-service-and-support\n  - communications\n  - marketing\n  - mobile-marketing\ndescription:\n  it:\n    apiDocumentation: 'https://developer.tiledesk.com/apis/rest-api'\n    documentation: 'https://gethelp.tiledesk.com'\n    features:\n      - live chat widget\n      - chatbot\n      - 'integration with DialogFlow, Rasa and all major AI solutions'\n      - ticketing\n      - multi-project management\n      - SLAs setting\n      - 'smart assignment of the queues '\n      - seamless conversation\n      - dashboard\n      - knowledge base\n      - mobile apps\n    genericName: Live chat with integrated chatbots\n    longDescription: |\n      To have fully satisfied customers is complex. Conversation’s actors\n      (chatbots and humans) should handoff to each other many times during a\n      chat to address customers needs. \n\n\n      Tiledesk provides a solution **orchestrating instant, asynchronous and\n      multichannel communication among all stakeholders of a typical\n      conversational architecture**: chatbots, support operators, end users,\n      colleagues, applications, etc.  \n\n\n      **Main Features**: \n\n\n      - Live Chat Widget with full multichannel experience on Web and Mobile; \n\n      - Resolution Bot to automate customer support; \n\n      - Easy Integration with all major AI-platforms, cloud and Open source,\n      from DialogFlow to RASA; \n\n      - Ticketing Management system fully integrated into the platform and\n      instant conversation flow; \n\n      - Team Organization with multi-project management, SLAs setting, smart\n      assignment of the \n       queues, departments organization and much more; \n      - Seamless conversation to “jump” between different channels in a\n      transparent way for end \n       customers and agents; \n      - Chat Tools like typing indicator, off-line access, delivery receipts,\n      contact list and much more; \n\n      - Dashboard with real time analytics; \n\n      - Knowledge base.\n\n           \n\n      Tiledesk is an **open source **project, made with passion in Salento\n      (Southern Italy).\n    shortDescription: |-\n      Tiledesk delivers scalable customer service to your mobile apps and your\n      website. It is a conversational platform that connects chatbots and\n      humans \ndevelopmentStatus: stable\nit:\n  conforme:\n    gdpr: false\n    lineeGuidaDesign: false\n    misureMinimeSicurezza: false\n    modelloInteroperabilita: false\n  countryExtensionVersion: '0.2'\n  piattaforme:\n    anpr: false\n    cie: false\n    pagopa: false\n    spid: false\nlandingURL: 'https://tiledesk.com/install'\nlegal:\n  license: AGPL-3.0-only\nlocalisation:\n  availableLanguages:\n    - it\n    - en\n    - es\n    - fr\n    - pt\n    - de\n    - ru\n    - tr\n  localisationReady: true\nmaintenance:\n  contacts:\n    - name: Andrea Sponziello\n  type: internal\nname: Tiledesk\nplatforms:\n  - web\nreleaseDate: '2022-06-15'\nsoftwareType: standalone/web\nurl: 'https://github.com/Tiledesk/tiledesk-deployment'\n"
  },
  {
    "path": "pubmodules/activities/activityArchiver.js",
    "content": "const authEvent = require('../../event/authEvent');\nconst requestEvent = require('../../event/requestEvent');\nvar Activity = require('./models/activity');\nvar winston = require('../../config/winston');\n\nclass ActivityArchiver {\n\n    listen() {\n\n        winston.debug('ActivityArchiver listen');   \n\n        var enabled = process.env.ACTIVITY_HISTORY_ENABLED || \"false\";\n        winston.debug('ActivityArchiver enabled:'+enabled);\n    \n        if (enabled===\"true\") {\n          winston.debug('ActivityArchiver enabled');\n        }else {\n          winston.info('ActivityArchiver disabled');\n          return 0;\n        }\n\n        if (process.env.MONGOOSE_SYNCINDEX) {\n          Activity.syncIndexes();\n          winston.info(\"Activity.syncIndexes called\");\n        }\n        \n        var that = this;\n        \n        //modify all to async\n      \n      \n/*\n        activityEvent.on('user.verify.email', this.save);\n  \n        activityEvent.on('group.create', this.save);\n        activityEvent.on('group.update', this.save);\n        activityEvent.on('group.delete', this.save);\n\n        // activityEvent.on('lead.create', this.save);\n        activityEvent.on('lead.update', this.save);\n        activityEvent.on('lead.delete', this.save);\n        activityEvent.on('lead.download.csv', this.save);\n*/\n      \n//  works only if worker is disabled\n      var authProjectUserInvitePendingKey = 'project_user.invite.pending';  //Don't work if job_worker enabled because queue.worker is disabled\n      // if (authEvent.queueEnabled) { //queue not supported.\n      //   authProjectUserInvitePendingKey = 'project_user.invite.pending.queue';\n      // }\n      winston.debug('ActivityArchiver authProjectUserInvitePendingKey: ' + authProjectUserInvitePendingKey);\n\n        authEvent.on(authProjectUserInvitePendingKey,  function(event) { \n          setImmediate(() => {\n            if (event.skipArchive) {\n              return 0;\n            }\n              var activity = new Activity({actor: {type:\"user\", id: event.req.user.id, name: event.req.user.fullName }, \n              verb: \"PROJECT_USER_INVITE\", actionObj: event.req.body, \n              target: {type:\"pendinginvitation\", id:event.savedPendingInvitation._id.toString(), object: event.savedPendingInvitation }, \n              id_project: event.req.projectid });\n              that.save(activity);    \n               \n          });\n       \n        });\n      \n\n\n\n//  works only if worker is disabled\n        var authProjectUserInviteKey = 'project_user.invite';   //Don't work if job_worker enabled because queue.worker is disabled\n        // if (authEvent.queueEnabled) { //queue not supported\n        //   authProjectUserInviteKey = 'project_user.invite.queue';\n        // }\n        winston.debug('ActivityArchiver authProjectUserInviteKey: ' + authProjectUserInviteKey);\n  \n        authEvent.on(authProjectUserInviteKey,  function(event) { \n          setImmediate(() => {\n            if (event.skipArchive) {\n              return 0;\n            }\n\n\n            var activity = new Activity({actor: {type:\"user\", id: event.req.user.id, name: event.req.user.fullName }, \n                        verb: \"PROJECT_USER_INVITE\", actionObj: event.req.body, \n                        target: {type:\"project_user\", id: event.savedProject_userPopulated._id.toString(), object: event.savedProject_userPopulated }, \n                        id_project: event.req.projectid });\n              that.save(activity);   \n            \n          });\n        });\n\n\n\n      \n      // verified with queue\n        var authProjectUserUpdateKey = 'project_user.update';\n        if (authEvent.queueEnabled) {\n          authProjectUserUpdateKey = 'project_user.update.queue';\n        }\n        winston.debug('ActivityArchiver authProjectUserUpdateKey: ' + authProjectUserUpdateKey);\n  \n       authEvent.on(authProjectUserUpdateKey,  function(event) { \n        setImmediate(() => {   \n          // winston.info('ActivityArchiver authProjectUserUpdateKey: ', event);\n      \n         /*\n         2019-11-20T10:40:52.686991+00:00 app[web.1]: TypeError: Cannot read property '_id' of undefined\n*/\n          if (event.skipArchive) {\n            return 0;\n          }\n\n          var project_user = undefined;\n          if (event.updatedProject_userPopulated.toObject) {\n            project_user = event.updatedProject_userPopulated.toObject() \n          } else {\n            project_user = event.updatedProject_userPopulated;\n          }\n\n          winston.debug('ActivityArchiver authProjectUserUpdateKey event: ', event);\n\n          if (!event.req.user) {\n            return  winston.debug('ActivityArchiver skipping archive empty user'); //from i think chat21webhook\n          }\n          var activity = new Activity({actor: {type:\"user\", id: event.req.user._id, name: event.req.user.fullName }, \n                verb: \"PROJECT_USER_UPDATE\", actionObj: event.req.body, \n                target: {type:\"project_user\", id: event.updatedProject_userPopulated._id.toString(), object: project_user}, \n                id_project: event.updatedProject_userPopulated.id_project });\n          that.save(activity);    \n        \n      });\n        \n       });\n      \n//  works only if worker is disabled\n       var authProjectUserDeleteKey = 'project_user.delete';     //Don't work if job_worker enabled because queue.worker is disabled\n      //  if (authEvent.queueEnabled) { //queue not supported\n      //   authProjectUserDeleteKey = 'project_user.delete.queue';\n      //  }\n       winston.debug('ActivityArchiver authProjectUserDeleteKey: ' + authProjectUserDeleteKey);\n \n\n      authEvent.on(authProjectUserDeleteKey,  function(event) { \n        setImmediate(() => {\n          if (event.skipArchive) {\n            return 0;\n          }\n\n            var activity = new Activity({actor: {type:\"user\", id: event.req.user.id, name: event.req.user.fullName }, \n            verb: \"PROJECT_USER_DELETE\", actionObj: event.req.body, \n            target: {type:\"project_user\", id:event.req.params.project_userid, object: event.project_userPopulated.toObject() }, //Error saving activity Maximum call stack size exceeded\n            id_project: event.req.projectid });\n            that.save(activity);    \n          \n        });\n      });\n\n\n      \n// disabled why? performance?\n      // var authUserSignineKey = 'user.signin';\n      // // if (authEvent.queueEnabled) { //queue not supported\n      // //   authUserSignineKey = 'user.signin.queue';\n      // // }\n      // winston.info('ActivityArchiver authUserSignineKey: ' + authUserSignineKey);\n\n\n      //   authEvent.on(authUserSignineKey,  function(event) { \n      //       winston.info('ActivityArchiver user.login');\n      //     setImmediate(() => {\n           \n            \n      //       if (event.skipArchive) {\n      //         return 0;\n      //       }\n\n      //         var activity = new Activity({actor: {type:\"user\", id: event.user._id, name: event.user.fullName }, \n      //                 verb: \"USER_SIGNIN\", actionObj: event.req.body, //insecure it store password\n      //                 target: {type:\"user\", id:event.user._id.toString(), object: null }, \n      //                 id_project: '*' }); /// * project\n      //           that.save(activity);   \n            \n      //     });   \n      //   });\n\n\n        // var authUserSigninErrorKey = 'user.login.error';\n        // // if (authEvent.queueEnabled) {  //queue not supported\n        // //   authUserSigninErrorKey = 'user.login.error.queue';\n        // // }\n        // winston.info('ActivityArchiver authUserSigninErrorKey: ' + authUserSigninErrorKey);\n\n        \n        // authEvent.on(authUserSigninErrorKey,  function(event) { \n        //   setImmediate(() => {\n            \n        //     if (event.skipArchive) {\n        //       return 0;\n        //     }\n\n        //       var activity = new Activity({actor: {type:\"user\"}, \n        //       verb: \"USER_SIGNIN_ERROR\", actionObj: event.req.body,     //insecure it store password\n        //       target: {type:\"user\", id:null, object: null }, \n        //       id_project: '*' });     /// * project\n        //       that.save(activity);     \n            \n        //   });\n        // });\n      \n\n      //   var authUserResetPasswordKey = 'user.requestresetpassword';\n      //   // if (authEvent.queueEnabled) {  //queue not supported\n      //   //   authUserResetPasswordKey = 'user.requestresetpassword.queue';\n      //   // }\n      //   winston.info('ActivityArchiver authUserResetPasswordKey: ' + authUserResetPasswordKey);\n\n      //  authEvent.on(authUserResetPasswordKey,  function(event) {   \n      //   setImmediate(() => {   \n         \n      //     if (event.skipArchive) {\n      //       return 0;\n      //     }\n\n      //       var activity = new Activity({actor: {type:\"user\", id: event.updatedUser._id, name: event.updatedUser.fullName }, \n      //         verb: \"USER_REQUEST_RESETPASSWORD\", actionObj: event.req.body, \n      //         target: {type:\"user\", id:event.updatedUser._id.toString(), object: null }, \n      //         id_project: '*' });        /// * project\n      //       that.save(activity);       \n        \n      //   });\n      //  });\n      \n\n      //  var authUserResetPassword2Key = 'user.resetpassword';\n      // //  if (authEvent.queueEnabled) {  //queue not supported\n      // //   authUserResetPassword2Key = 'user.resetpassword.queue';\n      // //  }\n      //  winston.info('ActivityArchiver authUserResetPassword2Key: ' + authUserResetPassword2Key);\n\n\n      // authEvent.on(authUserResetPassword2Key,  function(event) {     \n      //   setImmediate(() => {     \n         \n      //     if (event.skipArchive) {\n      //       return 0;\n      //     }\n\n      //     var activity = new Activity({actor: {type:\"user\", id: event.saveUser._id, name: event.saveUser.fullName }, \n      //       verb: \"USER_RESETPASSWORD\", actionObj: null, //req.body otherwise print password  \n      //       target: {type:\"user\", id:event.saveUser._id.toString(), object: null }, \n      //       id_project: '*' });     /// * project\n      //     that.save(activity);     \n          \n      //   });\n      // });\n      \n      \n      // var authUserSignupKey = 'user.signup';\n      // //  if (authEvent.queueEnabled) {  //queue not supported\n      // //   authUserSignupKey = 'user.signup.queue';\n      // //  }\n      //  winston.info('ActivityArchiver authUserSignupKey: ' + authUserSignupKey);\n\n\n      //  authEvent.on(authUserSignupKey,  function(event) { \n      //   setImmediate(() => {      \n      //     if (event.skipArchive) {\n      //       return 0;\n      //     }\n\n      //       var activity = new Activity({actor: {type:\"user\", id: event.savedUser._id, name: event.savedUser.fullName }, \n      //             verb: \"USER_SIGNUP\", actionObj: event.req.body, \n      //             target: {type:\"user\", id: event.savedUser._id.toString(), object: null }, \n      //             id_project: '*' });     /// * project\n      //         that.save(activity);      \n           \n      //   });\n      //  });\n      \n\n      //  var authUserSignupErrorKey = 'user.signup.error';\n      // //  if (authEvent.queueEnabled) {   //queue not supported\n      // //   authUserSignupErrorKey = 'user.signup.error.queue';\n      // //  }\n      //  winston.info('ActivityArchiver authUserSignupErrorKey: ' + authUserSignupErrorKey);\n\n\n      // authEvent.on(authUserSignupErrorKey,  function(event) { \n      //   setImmediate(() => {     \n      //     if (event.skipArchive) {\n      //       return 0;\n      //     }\n\n\n      //     var activity = new Activity({actor: {type:\"user\"}, \n      //         verb: \"USER_SIGNUP_ERROR\", actionObj: event.req.body, \n      //         target: {type:\"user\", id:null, object: null }, \n      //         id_project: '*' });     /// * project\n      //       that.save(activity);    \n          \n      //   });\n      //  });\n      \n\n       var requestCreateKey = 'request.create';\n       if (requestEvent.queueEnabled) {\n         requestCreateKey = 'request.create.queue';\n       }\n       winston.debug('ActivityArchiver requestCreateKey: ' + requestCreateKey);\n\n        requestEvent.on(requestCreateKey,  function(request) {   \n          setImmediate(() => {        \n            winston.debug('ActivityArchiver requestCreate triggered');   \n          // problema requester_id\n\n          // Error saving activity  {\"_id\":\"5e06189c6e226d358896d733\",\"actor\":{\"_id\":\"5e06189c6e226d358896d734\",\"type\":\"user\",\"id\":null},\"verb\":\"REQUEST_CREATE\",\"actionObj\":{\"status\":200,\"participants\":[\"5e06189c6e226d358896d728\"],\"messages_count\":0,\"tags\":[],\"_id\":\"5e06189c6e226d358896d72e\",\"request_id\":\"request_id-closeRequest\",\"first_text\":\"first_text\",\"department\":{\"routing\":\"assigned\",\"default\":true,\"status\":1,\"_id\":\"5e06189c6e226d358896d72b\",\"name\":\"Default Department\",\"id_project\":\"5e06189c6e226d358896d729\",\"createdBy\":\"5e06189c6e226d358896d728\",\"createdAt\":\"2019-12-27T14:43:40.327Z\",\"updatedAt\":\"2019-12-27T14:43:40.327Z\",\"__v\":0},\"agents\":[{\"_id\":\"5e06189c6e226d358896d72a\",\"id_project\":\"5e06189c6e226d358896d729\",\"id_user\":\"5e06189c6e226d358896d728\",\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e06189c6e226d358896d728\",\"createdAt\":\"2019-12-27T14:43:40.324Z\",\"updatedAt\":\"2019-12-27T14:43:40.324Z\",\"__v\":0}],\"id_project\":\"5e06189c6e226d358896d729\",\"createdBy\":\"requester_id1\",\"channel\":{\"name\":\"chat21\"},\"createdAt\":\"2019-12-27T14:43:40.586Z\",\"updatedAt\":\"2019-12-27T14:43:40.586Z\",\"__v\":0},\"target\":{\"type\":\"request\",\"id\":\"5e06189c6e226d358896d72e\",\"object\":{\"status\":200,\"participants\":[\"5e06189c6e226d358896d728\"],\"messages_count\":0,\"tags\":[],\"_id\":\"5e06189c6e226d358896d72e\",\"request_id\":\"request_id-closeRequest\",\"first_text\":\"first_text\",\"department\":{\"routing\":\"assigned\",\"default\":true,\"status\":1,\"_id\":\"5e06189c6e226d358896d72b\",\"name\":\"Default Department\",\"id_project\":\"5e06189c6e226d358896d729\",\"createdBy\":\"5e06189c6e226d358896d728\",\"createdAt\":\"2019-12-27T14:43:40.327Z\",\"updatedAt\":\"2019-12-27T14:43:40.327Z\",\"__v\":0},\"agents\":[{\"_id\":\"5e06189c6e226d358896d72a\",\"id_project\":\"5e06189c6e226d358896d729\",\"id_user\":\"5e06189c6e226d358896d728\",\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e06189c6e226d358896d728\",\"createdAt\":\"2019-12-27T14:43:40.324Z\",\"updatedAt\":\"2019-12-27T14:43:40.324Z\",\"__v\":0}],\"id_project\":\"5e06189c6e226d358896d729\",\"createdBy\":\"requester_id1\",\"channel\":{\"name\":\"chat21\"},\"createdAt\":\"2019-12-27T14:43:40.586Z\",\"updatedAt\":\"2019-12-27T14:43:40.586Z\",\"__v\":0}},\"id_project\":\"5e06189c6e226d358896d729\"}\n\n          // TODO error: Error saving activity  {\"activity\":{\"_id\":\"5e273b31f13e801703d52515\",\"actor\":{\"_id\":\"5e273b31f13e801703d52516\",\"type\":\"user\",\"id\":null},\"verb\":\"REQUEST_CREATE\",\"actionObj\":{\"status\":200,\"participants\":[\"5e273b30f13e801703d52508\"],\"messages_count\":0,\"tags\":[],\"_id\":\"5e273b31f13e801703d52511\",\"request_id\":\"request_id1\",\"first_text\":\"first_text\",\"department\":{\"routing\":\"assigned\",\"default\":false,\"status\":1,\"_id\":\"5e273b31f13e801703d5250f\",\"name\":\"PooledDepartment-for-createWithIdWith\",\"id_project\":\"5e273b31f13e801703d5250a\",\"createdBy\":\"5e273b30f13e801703d52507\",\"createdAt\":\"2020-01-21T17:56:01.471Z\",\"updatedAt\":\"2020-01-21T17:56:01.471Z\",\"__v\":0},\"agents\":[{\"_id\":\"5e273b31f13e801703d5250b\",\"id_project\":\"5e273b31f13e801703d5250a\",\"id_user\":\"5e273b30f13e801703d52507\",\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e273b30f13e801703d52507\",\"createdAt\":\"2020-01-21T17:56:01.465Z\",\"updatedAt\":\"2020-01-21T17:56:01.465Z\",\"__v\":0},{\"_id\":\"5e273b31f13e801703d5250e\",\"id_project\":\"5e273b31f13e801703d5250a\",\"id_user\":\"5e273b30f13e801703d52508\",\"role\":\"agent\",\"user_available\":true,\"createdBy\":\"5e273b30f13e801703d52508\",\"createdAt\":\"2020-01-21T17:56:01.469Z\",\"updatedAt\":\"2020-01-21T17:56:01.469Z\",\"__v\":0}],\"id_project\":\"5e273b31f13e801703d5250a\",\"createdBy\":\"requester_id1\",\"channel\":{\"name\":\"chat21\"},\"createdAt\":\"2020-01-21T17:56:01.480Z\",\"updatedAt\":\"2020-01-21T17:56:01.480Z\",\"__v\":0},\"target\":{\"type\":\"request\",\"id\":\"5e273b31f13e801703d52511\",\"object\":{\"status\":200,\"participants\":[\"5e273b30f13e801703d52508\"],\"messages_count\":0,\"tags\":[],\"_id\":\"5e273b31f13e801703d52511\",\"request_id\":\"request_id1\",\"first_text\":\"first_text\",\"department\":{\"routing\":\"assigned\",\"default\":false,\"status\":1,\"_id\":\"5e273b31f13e801703d5250f\",\"name\":\"PooledDepartment-for-createWithIdWith\",\"id_project\":\"5e273b31f13e801703d5250a\",\"createdBy\":\"5e273b30f13e801703d52507\",\"createdAt\":\"2020-01-21T17:56:01.471Z\",\"updatedAt\":\"2020-01-21T17:56:01.471Z\",\"__v\":0},\"agents\":[{\"_id\":\"5e273b31f13e801703d5250b\",\"id_project\":\"5e273b31f13e801703d5250a\",\"id_user\":\"5e273b30f13e801703d52507\",\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e273b30f13e801703d52507\",\"createdAt\":\"2020-01-21T17:56:01.465Z\",\"updatedAt\":\"2020-01-21T17:56:01.465Z\",\"__v\":0},{\"_id\":\"5e273b31f13e801703d5250e\",\"id_project\":\"5e273b31f13e801703d5250a\",\"id_user\":\"5e273b30f13e801703d52508\",\"role\":\"agent\",\"user_available\":true,\"createdBy\":\"5e273b30f13e801703d52508\",\"createdAt\":\"2020-01-21T17:56:01.469Z\",\"updatedAt\":\"2020-01-21T17:56:01.469Z\",\"__v\":0}],\"id_project\":\"5e273b31f13e801703d5250a\",\"createdBy\":\"requester_id1\",\"channel\":{\"name\":\"chat21\"},\"createdAt\":\"2020-01-21T17:56:01.480Z\",\"updatedAt\":\"2020-01-21T17:56:01.480Z\",\"__v\":0}},\"id_project\":\"5e273b31f13e801703d5250a\"},\"err\":{\"errors\":{\"actor.id\":{\"message\":\"Path `id` is required.\",\"name\":\"ValidatorError\",\"properties\":{\"message\":\"Path `id` is required.\",\"type\":\"required\",\"path\":\"id\",\"value\":null},\"kind\":\"required\",\"path\":\"id\",\"value\":null},\"actor\":{\"errors\":{\"id\":{\"message\":\"Path `id` is required.\",\"name\":\"ValidatorError\",\"properties\":{\"message\":\"Path `id` is required.\",\"type\":\"required\",\"path\":\"id\",\"value\":null},\"kind\":\"required\",\"path\":\"id\",\"value\":null}},\"_message\":\"Validation failed\",\"message\":\"Validation failed: id: Path `id` is required.\",\"name\":\"ValidationError\"}},\"_message\":\"activity validation failed\",\"message\":\"activity validation failed: actor.id: Path `id` is required., actor: Validation failed: id: Path `id` is required.\",\"name\":\"ValidationError\"}}\n\n// request is plain object must be mongoose object oto populate\n\n          // if (event.skipArchive) {\n          //   return 0;\n          // }\n\n          //TODO remove preflight   \n\n              try {\n\n                if (request.preflight === true) {\n                  winston.debug(\"preflight request disable archiver\")\n                  return 0;\n                }\n                var activity = new Activity({actor: {type:\"user\", id: request.requester_id}, \n                verb: \"REQUEST_CREATE\", actionObj: request, \n                target: {type:\"request\", id:request._id, object: request }, \n                id_project: request.id_project });\n                that.save(activity);    \n              } catch(e) {\n                winston.error('ActivityArchiver error saving activity',e);\n              }\n          \n                \n          });                           \n        });\n\n\n        // verified with queue\n        var requestUpdatePreflightKey = 'request.update.preflight';\n        if (requestEvent.queueEnabled) {\n          requestUpdatePreflightKey = 'request.update.preflight.queue';\n        }\n        winston.debug('ActivityArchiver requestUpdatePreflightKey: ' + requestUpdatePreflightKey);\n\n        \n        requestEvent.on(requestUpdatePreflightKey,  function(request) {\n          winston.debug('ActivityArchiver request.update.preflight: ');\n   \n          setImmediate(() => {           \n       \n              try {\n\n                if (request.preflight === true) {\n                  winston.debug(\"preflight request disable archiver\")\n                  return 0;\n                }\n                var activity = new Activity({actor: {type:\"user\", id: request.requester_id}, \n                verb: \"REQUEST_CREATE\", actionObj: request, \n                target: {type:\"request\", id:request._id, object: request }, \n                id_project: request.id_project });\n                that.save(activity);    \n              } catch(e) {\n                winston.error('ActivityArchiver error saving activity',e);\n              }\n          \n                \n          });                           \n        });\n\n\n\n        // verified with queue\n        var requestCloseKey = 'request.close';   //request.close event here queued under job\n        if (requestEvent.queueEnabled) {\n          requestCloseKey = 'request.close.queue';\n        }\n        winston.debug('ActivityArchiver requestCloseKey: ' + requestCloseKey);\n\n\n        requestEvent.on(requestCloseKey,  function(request) {   \n          winston.debug(\"request.close event here 7\")\n          setImmediate(() => {           \n       \n              try {\n                winston.debug('ActivityArchiver close');\n               \n                var activity = new Activity({actor: {type:\"user\", id: request.closed_by}, \n                verb: \"REQUEST_CLOSE\", actionObj: request, \n                target: {type:\"request\", id:request._id, object: request }, \n                id_project: request.id_project });\n                that.save(activity);    \n              } catch(e) {\n                winston.error('ActivityArchiver error saving activity',e);\n              }\n          \n                \n          });                           \n        });\n\n\n        \n\n        winston.info('ActivityArchiver listening');\n\n    }\n\n    save(activity) {\n        activity.save(function(err, savedActivity) {\n            if (err) {\n                winston.error('Error saving activity ', {activity: activity.toObject(), err:err});\n            }else {\n                winston.debug('Activity saved', savedActivity.toObject());\n            }\n        });\n    }\n}\n\nvar activityArchiver = new ActivityArchiver();\n\n\nmodule.exports = activityArchiver;"
  },
  {
    "path": "pubmodules/activities/index.js",
    "content": "const activityRoute = require(\"./routes/activity\");\nconst activityArchiver = require(\"./activityArchiver\");\nmodule.exports = {activityArchiver:activityArchiver,activityRoute:activityRoute};"
  },
  {
    "path": "pubmodules/activities/models/activity.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n\n// https://getstream.io/blog/designing-activity-stream-newsfeed-w3c-spec/\n// {\n//   \"@context\": \"http://www.w3.org/ns/activitystreams\",\n//   \"type\": \"added\",\n//   \"published\": \"2015-02-10T15:04:55Z\",\n//   \"actor\": {\n//    \"type\": \"Person\",\n//    \"id\": \"http://www.test.example/jack\",\n//    \"name\": \"Jack Hill\",\n//    \"url\": \"http://example.org/jack\",\n//    \"image\": {\n//      \"type\": \"Link\",\n//      \"href\": \"http://example.org/jack/profile.jpg\",\n//      \"mediaType\": \"image/jpeg\"\n//    }\n//   },\n//   \"object\" : {\n//    \"id\": \"http://www.test.example/jack/hill_photos/the_hill1.jpg\",\n//    \"type\": \"Photo\",\n//    \"label\": \"Great Photo of The Hill\"\n//   },\n//   \"target\" : {\n//    \"id\": \"http://example.org/jack/albums/great_hill_pics\",\n//    \"type\": \"OrderedCollection\",\n//    \"name\": \"Great Hill Pics\"\n//   }\n//  }\n\n//actor: {type:\"user\", id: event.req.user.id, name: event.req.user.fullName },\nvar ActorActivitySchema = new Schema({\n  type: { \n    type: String,\n    required: true,\n    index:true\n  },\n  id: {\n    type: String,\n    required: true,\n    index:true\n  },\n  name: {\n    type: String,\n    required: false\n  },\n});\n\nvar ActivitySchema = new Schema({\n  \n  actor: { \n    // type: String,\n    type: ActorActivitySchema,\n    required: true,\n    //index: true    //error saving activity Btree::insert: key too large to index, failing heroku_hwhg3xtx.activities.$target_1 2359 { : { object: { __v: 0, updatedAt: new Date(1555407053615), createdAt: new Date(1555407053615), request_id: \"support-group-Lc_Tz_hoCZ9REHC9FbY\", requester_id: \"5c8a38012d8e6d0017bce22a\", first_text: \"test 81\", department: ObjectId('5b8eb48b5ca4d300141fb2cb'), sourcePage: \"https://www.tiledesk.com/\", language: \"it\", \n  },\n  verb: {\n    type: String,\n    required: true,\n    index: true\n  },\n\n  actionObj: {\n    type: Object,\n    required: false\n  },\n  target: {\n    // type: String,\n    type: Object,\n    required: true,\n    // index: true\n  },\n  // summary  A natural language summarization of the object encoded as HTML. Multiple language tagged summaries may be provided.\n  // summaryMap https://www.w3.org/TR/activitystreams-vocabulary/#dfn-summary\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  }\n},{\n  timestamps: true\n}\n);\n\nActivitySchema.index({id_project: 1, createdAt: -1});\n\n// TODO metti indice per query è lentina\n\nmodule.exports = mongoose.model('activity', ActivitySchema);\n"
  },
  {
    "path": "pubmodules/activities/routes/activity.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Activity = require(\"../models/activity\");\nvar winston = require('../../../config/winston');\nvar moment = require('moment');\nvar ObjectId = require('mongoose').Types.ObjectId;\n\ncsv = require('csv-express');\ncsv.separator = ';';\n// csv = require('csv-express');\n// csv.separator = ';';\n\n\n// router.post('/', function (req, res) {\n\n//   winston.debug(req.body);\n//   winston.debug(\"req.user\", req.user);\n\n//   var newLead = new Lead({\n//     fullname: req.body.fullname,\n//     lead_id: req.body.lead_id,\n//     email: req.body.email,\n//     id_project: req.projectid,\n//     createdBy: req.user.id,\n//     updatedBy: req.user.id\n//   });\n\n//   newLead.save(function (err, savedLead) {\n//     if (err) {\n//       winston.debug('--- > ERROR ', err)\n//       return res.status(500).send({ success: false, msg: 'Error saving object.' });\n//     }\n//     res.json(savedLead);\n//   });\n// });\n\n// router.get('/:leadid', function (req, res) {\n//   winston.debug(req.body);\n\n//   Lead.findById(req.params.leadid, function (err, lead) {\n//     if (err) {\n//       return res.status(500).send({ success: false, msg: 'Error getting object.' });\n//     }\n//     if (!lead) {\n//       return res.status(404).send({ success: false, msg: 'Object not found.' });\n//     }\n//     res.json(lead);\n//   });\n// });\n\n\n\nrouter.get('/', function (req, res) {\n  var limit = 40; // Number of activities per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('Activity ROUTE - SKIP PAGE ', skip);\n  // winston.debug('Activity ROUTE - SKIP PAGE ', skip);\n\n  winston.debug('Activity ROUTE - QUERY ', req.query)\n\n\n  var query = { \"id_project\": req.projectid };\n\n\n  /**\n   * DATE RANGE  */\n  if (req.query.start_date && req.query.end_date) {\n    winston.debug('Activity ROUTE - REQ QUERY start_date ', req.query.start_date);\n    winston.debug('Activity ROUTE - REQ QUERY end_date ', req.query.end_date);\n\n    /**\n     * USING MOMENT      */\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n    var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED START DATE ', startDate);\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED END DATE ', endDate);\n\n    // ADD ONE DAY TO THE END DAY\n    var date = new Date(endDate);\n    var newdate = new Date(date);\n    var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);\n    // var endDate_plusOneDay =   moment('2018-09-03').add(1, 'd')\n    // var endDate_plusOneDay =   endDate.add(1).day();\n    // var toDate = new Date(Date.parse(endDate_plusOneDay)).toISOString()\n\n    query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }\n    winston.debug('Activity ROUTE - QUERY CREATED AT ', query.createdAt);\n\n  } else if (req.query.start_date && !req.query.end_date) {\n    winston.debug('Activity ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString() };\n    winston.debug('Activity ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);\n  }\n\n  if (req.query.agent_id) {\n    winston.debug('req.query.agent', req.query.agent_id);\n    query[\"$or\"] = [\n      { \"target.object.id_user._id\": new ObjectId(req.query.agent_id) },\n      { \"actor.id\": req.query.agent_id }\n    ];\n\n    // query[\"$or\"] = [\n    //   { $or: [{\"target.object.id_user._id\": new ObjectId(req.query.agent_id) }, { \"actor.id\": new ObjectId(req.query.agent_id) }]}\n    // ];\n  }\n\n  if (req.query.activities) {\n    winston.debug('req.query.activities:', req.query.activities);\n\n    let verbs = req.query.activities.split(\",\")\n\n    winston.debug('verbs: ', verbs);\n    query.verb = verbs;\n\n    // to test\n    // query.verb = ['PROJECT_USER_DELETE','PROJECT_USER_INVITE']\n  }\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  winston.debug(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  winston.debug(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  winston.debug('Activity ROUTE - Activity.find(query) ', query)\n  return Activity.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, activities) {\n      if (err) {\n        winston.error('Activity ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n      return Activity.countDocuments(query, function (err, totalRowCount) {\n        if (err) {\n          winston.error('Activity ROUTE - REQUEST FIND ERR ', err)\n          return (err);\n        }\n\n        var objectToReturn = {\n          perPage: limit,\n          count: totalRowCount,\n          activities: activities\n        };\n        winston.debug('Activity ROUTE - objectToReturn ', objectToReturn);\n\n        objectToReturn.activities.forEach(activity => {\n          winston.debug('Activity ROUTE - activity.target ', activity.target);\n          if (activity.target && activity.target.object && activity.target.object.id_user) {\n            winston.debug('Activity ROUTE - *** 9-+activity.target.id_user ', activity.target.object.id_user._id);\n          }\n        });\n\n        return res.json(objectToReturn);\n      });\n    });\n});\n\n// DOWNLOAD ACTIVITIES AS CSV\nrouter.get('/csv', function (req, res) {\n  var limit = 100000; // Number of activities per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('Activity ROUTE - SKIP PAGE ', skip);\n  // winston.debug('Activity ROUTE - SKIP PAGE ', skip);\n\n  winston.debug('Activity ROUTE - QUERY ', req.query)\n\n\n  var query = { \"id_project\": req.projectid };\n\n  /**\n   * DATE RANGE  */\n  if (req.query.start_date && req.query.end_date) {\n    winston.debug('Activity ROUTE - REQ QUERY start_date ', req.query.start_date);\n    winston.debug('Activity ROUTE - REQ QUERY end_date ', req.query.end_date);\n\n    /**\n     * USING MOMENT      */\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n    var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED START DATE ', startDate);\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED END DATE ', endDate);\n\n    // ADD ONE DAY TO THE END DAY\n    var date = new Date(endDate);\n    var newdate = new Date(date);\n    var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);\n    winston.debug('Activity ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);\n    // var endDate_plusOneDay =   moment('2018-09-03').add(1, 'd')\n    // var endDate_plusOneDay =   endDate.add(1).day();\n    // var toDate = new Date(Date.parse(endDate_plusOneDay)).toISOString()\n\n    query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }\n    winston.debug('Activity ROUTE - QUERY CREATED AT ', query.createdAt);\n\n  } else if (req.query.start_date && !req.query.end_date) {\n    winston.debug('Activity ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString() };\n    winston.debug('Activity ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);\n  }\n\n  if (req.query.agent_id) {\n    winston.debug('req.query.agent', req.query.agent_id);\n    query[\"$or\"] = [\n      { \"target.object.id_user._id\": new ObjectId(req.query.agent_id) },\n      { \"actor.id\": req.query.agent_id }\n    ];\n\n    // query[\"$or\"] = [\n    //   { $or: [{\"target.object.id_user._id\": new ObjectId(req.query.agent_id) }, { \"actor.id\": new ObjectId(req.query.agent_id) }]}\n    // ];\n  }\n\n  if (req.query.activities) {\n    winston.debug('req.query.activities:', req.query.activities);\n\n    let verbs = req.query.activities.split(\",\")\n\n    winston.debug('verbs: ', verbs);\n    query.verb = verbs;\n\n    // to test\n    // query.verb = ['PROJECT_USER_DELETE','PROJECT_USER_INVITE']\n  }\n\n  if (req.query.lang) {\n    winston.debug('req.query.lang:', req.query.lang);\n    var lang = req.query.lang;\n  }\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  winston.debug(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  winston.debug(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  winston.debug('Activity ROUTE - Activity.find(query) ', query)\n  return Activity.find(query).\n    skip(skip).limit(limit).\n    lean().\n    sort(sortQuery).\n    exec(function (err, activities) {\n      if (err) {\n        winston.error('Activity ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n      winston.debug('activities: ', activities);\n\n\n      // csvActivitiesToReturn = [];\n      csvActivitiesToReturn = buildCsv(activities, lang);\n\n\n      winston.debug('csvActivitiesToReturn: ', csvActivitiesToReturn);\n\n      return res.csv(csvActivitiesToReturn, true);\n\n\n    });\n\n\n  function translateString(string, lang) {\n    var translatedString = ''\n\n    if (string === 'hasChanged') {\n      if (lang === 'it') {\n        translatedString = 'ha cambiato'\n      } else {\n        translatedString = 'has changed'\n      }\n    }\n\n    if (string === 'hisStatus') {\n      if (lang === 'it') {\n        translatedString = ' il suo stato'\n      } else {\n        translatedString = ' his status'\n      }\n    }\n\n    if (string === 'theAvailabilityStatusOf') {\n      if (lang === 'it') {\n        translatedString = ' lo stato di disponibilità di'\n      } else {\n        translatedString = ' the availability status of'\n      }\n    }\n\n\n    if (string === 'theRoleOf') {\n      if (lang === 'it') {\n        translatedString = ' il ruolo di'\n      } else {\n        translatedString = ' the role of'\n      }\n    }\n\n    if (string === 'intoUnavailable') {\n      if (lang === 'it') {\n        translatedString = ' in non disponibile'\n      } else {\n        translatedString = ' into unavailable'\n      }\n    }\n\n    if (string === 'intoAvailable') {\n      if (lang === 'it') {\n        translatedString = ' in disponibile'\n      } else {\n        translatedString = ' into available'\n      }\n    }\n\n    if (string === 'intoAdministrator') {\n      if (lang === 'it') {\n        translatedString = ' in Amministratore'\n      } else {\n        translatedString = ' into Administrator'\n      }\n    }\n\n    if (string === 'intoAgent') {\n      if (lang === 'it') {\n        translatedString = ' in Agente'\n      } else {\n        translatedString = ' into Agent'\n      }\n    }\n\n\n    if (string === 'HasRemoved') {\n      if (lang === 'it') {\n        translatedString = ' ha rimosso'\n      } else {\n        translatedString = ' has removed'\n      }\n    }\n\n    if (string === 'FromTheProject') {\n      if (lang === 'it') {\n        translatedString = ' dal progetto'\n      } else {\n        translatedString = ' from the project'\n      }\n    }\n\n    if (string === 'HasInvited') {\n      if (lang === 'it') {\n        translatedString = ' ha invitato'\n      } else {\n        translatedString = ' has invited'\n      }\n    }\n\n    if (string === 'ToTakeOnTheRoleOf') {\n      if (lang === 'it') {\n        translatedString = ' ad assumere il ruolo di'\n      } else {\n        translatedString = ' to take on the role of'\n      }\n    }\n\n\n\n\n\n\n\n    return translatedString\n  }\n\n  function buildMsg_REQUEST_CREATE(lang, activity) {\n    winston.debug('buildMsg_REQUEST_CREATE - lang: ', lang, ' activity: ', activity)\n  }\n\n  function buildMsg_PROJECT_USER_INVITE(actor_name, target_fullname, lang, activity) {\n    var action = '';\n\n\n    action = translateString('HasInvited', lang) + ' ' + target_fullname\n\n\n    // email with round brackets if it not is a pendinginvitation\n    if (activity.target && activity.target.type !== 'pendinginvitation') {\n\n      if (activity.actionObj && activity.actionObj.email) {\n\n        action = action + '(' + activity.actionObj.email + ')'\n\n      } else {\n\n        // email without round brackets if it is a pendinginvitation\n\n        action = action + activity.actionObj.email\n      }\n\n    }\n\n    action = action + translateString('ToTakeOnTheRoleOf', lang)\n\n\n    return message = actor_name + action\n\n  }\n\n\n  function buildMsg_PROJECT_USER_DELETE(actor_name, target_fullname, lang) {\n    var action = '';\n    action = translateString('HasRemoved', lang) + ' ' + target_fullname + translateString('FromTheProject', lang)\n\n    return message = actor_name + action\n\n  }\n\n  function buildMsg_PROJECT_USER_UPDATE(actor_name, target_fullname, lang, activity) {\n\n    var action = '';\n\n    action = translateString('hasChanged', lang)\n    // if (lang === 'it') {\n    //   action = 'ha cambiato'\n    // } else {\n    //   action = 'has changed'\n    // }\n\n    if (activity.actor && activity.actor.id) {\n      if (activity.target &&\n        activity.target.object &&\n        activity.target.object.id_user &&\n        activity.target.object.id_user._id) {\n\n        var target_user_id = JSON.stringify(activity.target.object.id_user._id).replace(/['\"]+/g, '')\n\n        if (activity.actor.id === target_user_id) {\n          // USE CASE 1: THE TARGET OF THE ACTION IS THE CURRENT USER (YOURSELF) \n\n          action = action + translateString('hisStatus', lang)\n\n        } else {\n\n          // USE CASE 2: THE TARGET OF THE ACTION IS ANOTHER USER (NOT THE LOGGED USER)\n          winston.debug('here + activity.actionObj.user_available ', activity.actionObj.user_available)\n          if (activity.actionObj && activity.actionObj.user_available === false || activity.actionObj.user_available === true) {\n\n            winston.debug('here ++')\n\n            action = action + translateString('theAvailabilityStatusOf', lang)\n\n            // if (lang === 'it') {\n            //   action = action + ' lo stato di disponibilità di'\n            // } else {\n            //   action = action + ' the availability status of'\n            // }\n\n          }\n          else if (activity.actionObj && activity.actionObj.role) {\n\n            action = action + translateString('theRoleOf', lang)\n\n            // if (lang === 'it') {\n            //   action = action + ' il ruolo di'\n            // } else {\n            //   action = action + ' the role of'\n            // }\n          }\n\n          action = action + ' ' + target_fullname\n\n        }\n\n        if (activity.actionObj && activity.actionObj.user_available === false) {\n\n          action = action + translateString('intoUnavailable', lang);\n\n          // if (lang === 'it') {\n          //   action = action + ' in non disponibile'\n          // } else {\n          //   action = action + ' into unavailable'\n          // }\n\n        } else if (activity.actionObj && activity.actionObj.user_available === true) {\n\n          action = action + translateString('intoAvailable', lang);\n\n          // if (lang === 'it') {\n          //   action = action + ' in disponibile'\n          // } else {\n          //   action = action + ' into available'\n          // }\n        }\n\n        if (activity.actionObj && activity.actionObj.role === 'admin') {\n\n          action = action + translateString('intoAdministrator', lang);\n\n          // if (lang === 'it') {\n          //   action = action + ' in Amministratore'\n          // } else {\n          //   action = action + ' into Administrator'\n          // }\n\n        } else if (activity.actionObj && activity.actionObj.role === 'agent') {\n\n          action = action + translateString('intoAgent', lang);\n\n          // if (lang === 'it') {\n          //   action = action + ' in Agente'\n          // } else {\n          //   action = action + ' into Agent'\n          // }\n\n        }\n\n\n\n\n\n\n      }\n    }\n\n\n    return message = actor_name + ' ' + action\n  }\n\n\n  function buildCsv(activities, lang) {\n    csvActivitiesToReturn = []\n    activities.forEach(activity => {\n\n      // if (lang) {\n      winston.debug('buildCsv lang: ', lang);\n\n      var actor_name = '';\n      var target_fullname = '';\n      if (activity.actor) {\n        actor_name = activity.actor.name;\n\n      }\n\n      if (activity.target &&\n        activity.target.object &&\n        activity.target.object.id_user &&\n        activity.target.object.id_user.firstname) {\n\n        target_fullname = activity.target.object.id_user.firstname\n\n        if (activity.target.object.id_user.lastname) {\n\n          target_fullname = target_fullname + ' ' + activity.target.object.id_user.lastname\n        }\n\n      }\n\n      if (activity.verb === \"PROJECT_USER_UPDATE\") {\n        var message = buildMsg_PROJECT_USER_UPDATE(actor_name, target_fullname, lang, activity)\n      }\n\n      if (activity.verb === \"PROJECT_USER_DELETE\") {\n        var message = buildMsg_PROJECT_USER_DELETE(actor_name, target_fullname, lang)\n      }\n\n      if (activity.verb === \"PROJECT_USER_INVITE\") {\n        var message = buildMsg_PROJECT_USER_INVITE(actor_name, target_fullname, lang, activity)\n      }\n\n      if (activity.verb === \"REQUEST_CREATE\") {\n        var message = buildMsg_REQUEST_CREATE(lang, activity)\n      }\n\n\n      if (activity.actionObj && activity.actionObj.email) {\n        var actionObj_email = activity.actionObj.email;\n      } else {\n        var actionObj_email = \"\"\n      }\n\n      if (activity.actionObj && activity.actionObj.id_project) {\n        var actionObj_id_project = activity.actionObj.id_project;\n      } else {\n        var actionObj_id_project = \"\"\n      }\n\n      if (activity.actionObj && activity.actionObj.project_name) {\n        var actionObj_project_name = activity.actionObj.project_name;\n      } else {\n        var actionObj_project_name = \"\"\n      }\n\n      if (activity.actionObj && activity.actionObj.role) {\n        var actionObj_role = activity.actionObj.role;\n      } else {\n        var actionObj_role = \"\"\n      }\n\n      if (activity.actionObj && activity.actionObj.user_available) {\n        var actionObj_user_available = activity.actionObj.user_available;\n      } else {\n        var actionObj_user_available = \"\"\n      }\n\n      if (activity.target) {\n        var target_id = activity.target.id\n        var target_type = activity.target.type\n      }\n\n      if (activity.target && activity.target.object) {\n        var target_createdAt = activity.target.object.createdAt;\n        var target_createdBy = activity.target.object.createdBy;\n        var target_role = activity.target.object.role;\n        var target_user_available = activity.target.object.user_available;\n        // var target_id = activity.target.object._id;\n      }\n\n      if (activity.target && activity.target.object && activity.target.object.id_user) {\n        var target_user_fullname = activity.target.object.id_user.firstname + \" \" + activity.target.object.id_user.lastname;\n        var target_user_id = activity.target.object.id_user._id\n      } else {\n        var target_user_fullname = \"\";\n        var target_user_id = \"\";\n      }\n\n      if (activity.target && activity.target.object && activity.target.object.email) {\n        var target_email = activity.target.object.email;\n      } else {\n        var target_email = \"\";\n      }\n\n\n      csvActivitiesToReturn.push({\n        'message': message,\n        \"activity_id\": activity._id,\n        \"createdAt\": activity.createdAt,\n        \"updatedAt\": activity.updatedAt,\n        \"project_id\": activity.id_project,\n        \"actor_name\": activity.actor.name,\n        \"actor_id\": activity.actor.id,\n        \"actor_type\": activity.actor.type,\n        \"verb\": activity.verb,\n        \"target_user_fullname\": target_user_fullname,\n        \"target_email\": target_email,\n        \"target_type\": target_type,\n        \"target_user_id\": target_user_id,\n        \"actionObj_email\": actionObj_email,\n        \"actionObj_id_project\": actionObj_id_project,\n        \"actionObj_project_name\": actionObj_project_name,\n        \"actionObj_role\": actionObj_role,\n        \"actionObj_user_available\": actionObj_user_available,\n        \"target_id\": target_id,\n        \"target_createdAt\": target_createdAt,\n        \"target_createdBy\": target_createdBy,\n        \"target_role\": target_role,\n        \"target_user_available\": target_user_available,\n      })\n\n    });\n\n    return csvActivitiesToReturn\n  }\n\n\n\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "pubmodules/activities/test/activityRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar Activity = require('../models/activity');\nvar projectService = require('../../../services/projectService');\nvar userService = require('../../../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../../../app');\nlet should = chai.should();\nvar winston = require('../../../config/winston');\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('ActivityRoute', () => {\n\n  describe('/create', () => {\n \n   \n\n    it('createAndList', (done) => {\n\n        \n    //   this.timeout();\n\n       var email = \"test-signup-\" + Date.now() + \"@email.com\";\n       var pwd = \"pwd\";\n\n        userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n            projectService.create(\"test-activity-create\", savedUser._id).then(function(savedProject) {\n             \n                var activity = new Activity(\n                    {\n                        actor: {type:\"user\", id: savedUser._id},\n                        verb: \"PROJECT_USER_UPDATE\", \n                        actionObj: {field:\"test\"}, \n                        target: '/testtarget', \n                        id_project: savedProject._id\n                    }\n                    );\n                activity.save(function(err, savedActivity) {\n                    if (err) {\n                        winston.error('Error saving activity ', err);\n                      }\n\n                    winston.info(\"savedActivity: \"  + savedActivity);\n\n                    chai.request(server)\n                        .get('/'+ savedProject._id + '/activities')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\",  res.body); }\n                            \n                            res.should.have.status(200);\n                            res.body.activities.should.be.a('array');\n                            expect(res.body.count).to.equal(1);                                                                              \n                        \n                            done();\n                        });\n\n                    });\n                });\n                });\n                \n    }).timeout(20000);\n\n\n\n\n\n\n\n\n});\n\n});\n\n\n"
  },
  {
    "path": "pubmodules/analytics/analytics.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar AnalyticResult = require(\"../../models/analyticResult\");\nvar AnalyticProject_UsersResult = require(\"../../models/analyticProject_usersResult\");\nvar AnalyticMessagesResult = require(\"../../models/analyticMessagesResult\");\nvar AnalyticEventsResult = require(\"../../pubmodules/events/analyticEventsResult\");\n\nvar RoleConstants = require(\"../../models/roleConstants\");\n\nvar mongoose = require('mongoose');\nvar winston = require('../../config/winston');\nvar Analytics = require('../../models/analytics');\nvar ObjectId = require('mongodb').ObjectId;\n\n\n\n// mongoose.set('debug', true);\n\n\n\n\nrouter.get('/requests/count', function(req, res) {\n  \n  winston.debug(req.params);\n  winston.debug(\"req.projectid\",  req.projectid);    \n \n    \n  AnalyticResult.aggregate([\n      // { \"$match\": {\"id_project\": req.projectid } },\n      // { \"$match\": {} },\n      { \"$match\": {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n      { \"$count\": \"totalCount\" }\n    \n  ])\n  // .exec((err, result) => {\n    .exec(function(err, result) {\n\n   \n//, function (err, result) {\n      if (err) {\n          winston.debug(err);\n          return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n        }\n        winston.debug(result);\n\n        res.json(result);\n  });\n\n});\n\n\n\nrouter.get('/requests/aggregate/status', function(req, res) {\n  \n  winston.debug(req.params);\n  winston.debug(\"req.projectid\",  req.projectid);    \n \n    \n  AnalyticResult.aggregate([\n      // { \"$match\": {\"id_project\": req.projectid } },\n      // { \"$match\": {} },\n      { \"$match\": {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n      { \"$count\": \"totalCount\" }\n    // DA IMPLEMNTARE\n  ])\n  // .exec((err, result) => {\n    .exec(function(err, result) {\n\n   \n//, function (err, result) {\n      if (err) {\n          winston.debug(err);\n          return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n        }\n        winston.debug(result);\n\n        res.json(result);\n  });\n\n});\n\n\n\n// https://stackoverflow.com/questions/22516514/mongodb-return-the-count-of-documents-for-each-day-for-the-last-one-month\n// https://stackoverflow.com/questions/15938859/mongodb-aggregate-within-daily-grouping\n// db.requests.aggregate(\n//   [\n//   // Get only records created in the last 30 days\n//   { $match: {\"id_project\":\"5ad5bd52c975820014ba900a\",\"createdAt\" : { $gte : new Date(ISODate().getTime() - 1000*60*60*24*30) }} },\n//   // Get the year, month and day from the createdTimeStamp\n//   {$project:{\n//         \"year\":{$year:\"$createdAt\"}, \n//         \"month\":{$month:\"$createdAt\"}, \n//         \"day\": {$dayOfMonth:\"$createdAt\"}\n//   }}, \n//   // Group by year, month and day and get the count\n//   {$group:{\n//         _id:{year:\"$year\", month:\"$month\", day:\"$day\"}, \n//         \"count\":{$sum:1}\n//   }},\n//   {$sort:{_id:1}},\n// ]\n// )\n\n  // router.get('/requests/aggregate/day', function(req, res) {\n    \n  //   //set default value for lastdays&department_id \n  //   let lastdays=7\n    \n    \n  //   //check for lastdays&dep_id parameters\n  //   if(req.query.lastdays){\n  //     lastdays=req.query.lastdays\n  //   }\n  \n  //   let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n\n  //   if(req.query.department_id){      \n  //     //add field departmentid to query if req.query.department_id exist\n  //     query.department=req.query.department_id;\n\n  //   }\n\n    \n    \n  //   winston.debug(\"QueryParams:\", lastdays,req.query.department_id)\n  //   winston.debug(\"Query\", query)\n\n  //   winston.debug(req.params);\n  //   winston.debug(\"req.projectid\",  req.projectid);    \n   \n  //   AnalyticResult.aggregate([\n  //       // { \"$match\": {\"id_project\": req.projectid } },\n  //       // { \"$match\": {} },\n  //       { $match: query },\n  //       { \"$project\":{\n  //         \"year\":{\"$year\":\"$createdAt\"}, \n  //         \"month\":{\"$month\":\"$createdAt\"}, \n  //         \"day\": {\"$dayOfMonth\":\"$createdAt\"}\n  //       }}, \n  //       // // Group by year, month and day and get the count\n  //       { \"$group\":{\n  //             \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\"}, \n  //             \"count\":{\"$sum\":1}\n  //       }},\n  //       { \"$sort\": {\"_id\":1}},  \n  //       // { \"$limit\": 7 },\n  //   ])\n  //   // .exec((err, result) => {\n  //     .exec(function(err, result) {\n\n     \n  // //, function (err, result) {\n  //       if (err) {\n  //           winston.debug(err);\n  //           winston.debug(\"ERR\",err)\n  //           return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n  //         }\n  //         winston.debug(result);\n  //         winston.debug(\"RES\",result)\n  //         res.json(result);\n  //   });\n\n  // });\n\n  router.get('/requests/aggregate/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n\n    if (req.query.participant) {\n      winston.debug('req.query.participant', req.query.participant);\n      query.participants = req.query.participant;\n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n    \n    winston.debug(\"QueryParams_LastDayCHART:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"}\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n\n\n  router.get('/requests/aggregate/status/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n\n    if (req.query.participant) {\n      winston.debug('req.query.participant', req.query.participant);\n      query.participants = req.query.participant;\n    }\n    \n    winston.debug(\"QueryParams_LastDayCHART:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"status\": \"$status\"\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\",\"status\": \"$status\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n\n  router.get('/requests/aggregate/month', function(req, res) {\n    \n    \n    let query={\"id_project\": req.projectid , $or:[ {preflight:false}, { preflight : { $exists: false } } ]}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n    \n    winston.debug(\"QueryParams_MonthCHART:\", req.query.department_id)\n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticResult.aggregate([\n         { \"$match\":  query},\n        // { \"$match\": {} },\n        //{ $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  router.get('/requests/aggregate/week', function(req, res) {\n    \n    \n    //let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      //query.department= new ObjectId(req.query.department_id);\n      \n    }\n    \n    //winston.debug(\"QueryParams_WeekCHART:\", lastdays,req.query.department_id)\n    //winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticResult.aggregate([\n         { \"$match\": {\"id_project\": req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ] } },\n        // { \"$match\": {} },\n        //{ $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"week\":{\"$week\":\"$createdAt\"},\n          \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\",\"week\":\"$week\", }, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  \n\n// db.requests.aggregate([\n  //   // Get only records created in the last 30 days\n  //   { $match: {\"id_project\":\"5ad5bd52c975820014ba900a\",\"createdAt\" : { $gte : new Date(ISODate().getTime() - 1000*60*60*24*30) }} },\n  //   // Get the year, month and day from the createdTimeStamp\n  //   {$project:{\n  //         \"hour\":{$hour:\"$createdAt\"}\n  //   }}, \n  //   // Group by year, month and day and get the count\n  //   {$group:{\n  //         _id:{hour:\"$hour\"}, \n  //         \"count\":{$sum:1}\n  //   }},\n  //   {$sort:{_id:-1}},\n  // ])\n\n  router.get('/requests/aggregate/hours', function(req, res) {  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    let timezone = req.query.timezone || \"+00:00\";\n    winston.debug(\"timezone\", timezone);\n\n    AnalyticResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n        { \"$project\":{\n          \"hour\":{\"$hour\": {date: \"$createdAt\", timezone: timezone} } \n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"hour\":\"$hour\"},\n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":-1}}\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n//test importante qui decommenta per vedere \n//   AnalyticResult.aggregate([\n//     // { \"$match\": {\"id_project\": req.projectid } },\n//     // { \"$match\": {} },\n//     { $match: {\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n//     { \"$project\":{\n//       \"hour\":{\"$hour\":\"$createdAt\"},\n//       \"weekday\":{\"$dayoftheweek\":\"$createdAt\"}\n//     }}, \n//     // // Group by year, month and day and get the count\n//     { \"$group\":{\n//           \"_id\":{\"hour\":\"$hour\",\"weekday\":\"$weekday\"},\n//           \"count\":{\"$sum\":1}\n//     }},\n//     { \"$sort\": {\"_id\":-1}}\n// ])\n  router.get('/requests/aggregate/dayoftheweek/hours', function(req, res) {  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);  \n\n    let timezone = req.query.timezone || \"+00:00\";\n    winston.debug(\"timezone\", timezone);\n\n    AnalyticResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n        { \"$project\":{\n            \"hour\":{\"$hour\":{date: \"$createdAt\", timezone: timezone} },\n            \"weekday\":{\"$dayOfWeek\":\"$createdAt\"}\n          }}, \n          // // Group by year, month and day and get the count\n          { \"$group\":{\n                \"_id\":{\"hour\":\"$hour\",\"weekday\":\"$weekday\"},\n                \"count\":{\"$sum\":1}\n          }},\n        { \"$sort\": {\"_id\":-1}}\n    ])\n      .exec(function(err, result) {\n\n     \n        if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n\n\n\n  // db.requests.aggregate([\n  //   { $match: {\"id_project\":\"5ad5bd52c975820014ba900a\"} },\n  //     { $group: { _id: \"$id_project\", \n  //       \"waiting_time_avg\":{$avg: \"$waiting_time\"}\n  //       }\n  //     },\n  //     { \"$sort\": {\"_id\":-1}},  \n  //   ])\n\n  \n    \n  router.get('/requests/waiting', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    var last = 30 * 24 * 60 * 60;\n      \n    if (req.query.last) {\n      last = req.query.last;\n    }\n\n    AnalyticResult.aggregate([\n      { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - ( last * 1000))) }} },\n        { \"$group\": { \n          \"_id\": \"$id_project\", \n         \"waiting_time_avg\":{\"$avg\": \"$waiting_time\"}\n        }\n      },\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n  \n\n  // db.requests.aggregate([\n  //   { $match: {\"id_project\":\"5ad5bd52c975820014ba900a\"} },\n  //    { $project:{\n        \n  //         \"month\":{$month:\"$createdAt\"},\n  //         \"year\":{$year:\"$createdAt\"},\n  //         \"waiting_time_project\" : \"$waiting_time\"\n  //       }}, \n  //     { $group: { _id: {month:\"$month\", year: \"$year\"}, \n  //       \"waiting_time_avg\":{$avg: \"$waiting_time_project\"}\n  //       }\n  //     },\n  //     { \"$sort\": {\"_id\":-1}},  \n  //   ])\n    \n\n  router.get('/requests/waiting/day/last', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        //{ \"$match\": {\"id_project\":req.projectid }},\n        { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (1 * 24 * 60 * 60 * 1000))) }} },\n        { \"$group\": { \n          \"_id\": \"$id_project\", \n         \"waiting_time_avg\":{\"$avg\": \"$waiting_time\"}\n        }\n      },\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n  router.get('/requests/waiting/day', function(req, res) {\n  \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n\n    if (req.query.participant) {\n      winston.debug('req.query.participant', req.query.participant);\n      query.participants = req.query.participant;\n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n\n    \n    winston.debug(\"QueryParams_AvgTime:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_AvgTIME\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\":{\"$dayOfMonth\":\"$createdAt\"},\n          \"waiting_time_project\" : \"$waiting_time\"\n        }}, \n        { \"$group\": { \n          \"_id\": {\"day\":\"$day\",\"month\":\"$month\", \"year\": \"$year\"}, \n         \"waiting_time_avg\":{\"$avg\": \"$waiting_time_project\"}\n        }\n      },\n      { \"$sort\": {\"_id\":-1}}\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RES\",result)\n          res.json(result);\n    });\n\n  });\n\n\n\n  router.get('/requests/waiting/month', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { \"$match\": {\"id_project\":req.projectid,$or:[ {preflight:false}, { preflight : { $exists: false } } ] }},\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"waiting_time_project\" : \"$waiting_time\"\n        }}, \n        { \"$group\": { \n          \"_id\": {\"month\":\"$month\", \"year\": \"$year\"}, \n         \"waiting_time_avg\":{\"$avg\": \"$waiting_time_project\"}\n        }\n      },\n      { \"$sort\": {\"_id\":-1}}\n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n//duration\n\n\n\n  // db.getCollection('requests').aggregate( [ \n  //   { $match: {\"closed_at\":{$exists:true}, \"id_project\":\"5ad5bd52c975820014ba900a\",\"createdAt\" : { $gte : new Date(ISODate().getTime() - 1000*60*60*24*1) }} },\n  //   {$project:{       \n  //         \"duration\": {$subtract: [\"$closed_at\",\"$createdAt\"]},\n  //         \"id_project\":1\n  //   }},\n  //   { \"$group\": { \n  //     \"_id\": \"$id_project\", \n  //    \"duration_avg\":{\"$avg\": \"$duration\"}\n  //   }   \n  //   }\n  // ] )\n\n\n  router.get('/requests/duration', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n      { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n      {$project:{       \n        \"duration\": {$subtract: [\"$closed_at\",\"$createdAt\"]},\n        \"id_project\":1\n      }},  \n      { \"$group\": { \n          \"_id\": \"$id_project\", \n         \"duration_avg\":{\"$avg\": \"$duration\"}\n        }\n      },\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n/*\n  db.getCollection('requests').aggregate( [ \n    { $match: {\"closed_at\":{$exists:true}, \"id_project\":\"5ad5bd52c975820014ba900a\",\"createdAt\" : { $gte : new Date(ISODate().getTime() - 1000*60*60*24*30) }} },\n    { \"$project\":{\n      \"year\":{\"$year\":\"$createdAt\"}, \n      \"month\":{\"$month\":\"$createdAt\"},\n      \"day\":{\"$dayOfMonth\":\"$createdAt\"},\n      \"duration\": {$subtract: [\"$closed_at\",\"$createdAt\"]},\n    }}, \n    { \"$group\": { \n      \"_id\": {\"day\":\"$day\",\"month\":\"$month\", \"year\": \"$year\"}, \n     \"duration_avg\":{\"$avg\": \"$duration\"}\n    }\n    },\n    { \"$sort\": {\"_id\":-1}}\n  ] )\n  */\n\n\n  router.get('/requests/duration/day', function(req, res) {\n  \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    \n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n    \n    if (req.query.participant) {\n      winston.debug('req.query.participant', req.query.participant);\n      query.participants = req.query.participant;\n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n\n    winston.debug(\"QueryParams_DurationTIME:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_DurationTIME\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\":{\"$dayOfMonth\":\"$createdAt\"},\n          \"duration\": {\"$subtract\": [\"$closed_at\",\"$createdAt\"]},\n        }}, \n        { \"$group\": { \n          \"_id\": {\"day\":\"$day\",\"month\":\"$month\", \"year\": \"$year\"}, \n          \"duration_avg\":{\"$avg\": \"$duration\"}\n        }\n      },\n      { \"$sort\": {\"_id\":-1}}\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n  router.get('/requests/duration/month', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { $match: {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"duration\": {\"$subtract\": [\"$closed_at\",\"$createdAt\"]},\n        }}, \n        { \"$group\": { \n          \"_id\": {\"month\":\"$month\", \"year\": \"$year\"}, \n          \"duration_avg\":{\"$avg\": \"$duration\"}\n        }\n      },\n      { \"$sort\": {\"_id\":-1}}\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n\n    \n  router.get('/requests/satisfaction', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { \"$match\": {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ]}},\n        { \"$group\": { \n          \"_id\": \"$id_project\", \n         \"satisfaction_avg\":{\"$avg\": \"$rating\"}\n        }\n      },\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n  \n  router.get('/requests/satisfaction/day', function(req, res) {\n  \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n\n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    \n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n    \n    if (req.query.participant) {\n      winston.debug('req.query.participant', req.query.participant);\n      query.participants = req.query.participant;\n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n    \n    winston.debug(\"QueryParams_SatisfactionTIME:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_SatisfactionTIME\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\":{\"$dayOfMonth\":\"$createdAt\"},\n          \"satisfaction_project\" : \"$rating\"\n        }}, \n        { \"$group\": { \n          \"_id\": {\"day\":\"$day\",\"month\":\"$month\", \"year\": \"$year\"}, \n          \"satisfaction_avg\":{\"$avg\": \"$satisfaction_project\"}\n        }\n      },\n      { \"$sort\": {\"_id\":-1}}\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n  router.get('/requests/satisfaction/month', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n      \n    AnalyticResult.aggregate([\n        { \"$match\": {\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ] }},\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"satisfaction_project\" : \"$rating\"\n        }}, \n        { \"$group\": { \n          \"_id\": {\"month\":\"$month\", \"year\": \"$year\"}, \n         \"satisfaction_avg\":{\"$avg\": \"$satisfaction_project\"}\n        }\n      },\n      \n    ])\n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n\nrouter.get('/requests/hasBot/count', function(req, res) {\n  \n  winston.debug(req.params);\n  winston.debug(\"req.projectid\",  req.projectid);    \n \n    \n  AnalyticResult.aggregate([\n      // { \"$match\": {\"id_project\": req.projectid } },\n      // { \"$match\": {} },\n      { \"$match\": {\"id_project\":req.projectid, hasBot:true, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n      { \"$count\": \"totalCount\" }\n    \n  ])\n  // .exec((err, result) => {\n    .exec(function(err, result) {\n\n   \n//, function (err, result) {\n      if (err) {\n          winston.debug(err);\n          return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n        }\n        winston.debug(result);\n\n        res.json(result);\n  });\n\n});\n\n\n\n\n  router.get('/requests/aggregate/hasBot/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n    \n    if(req.query.department_id){\n      //department_id=req.query.department_id;\n      //add field departmentid to query if req.query.department_id exist\n      query.department= new ObjectId(req.query.department_id);\n      \n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n    \n    winston.debug(\"QueryParams_LastDayCHART:\", lastdays,req.query.department_id)\n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"hasBot\": \"$hasBot\"\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\", \"hasBot\": \"$hasBot\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n\n  router.get('/requests/aggregate/sourcePage', function(req, res) {\n     //set default value for lastdays&department_id \n     let lastdays=30\n     //let department_id='';\n     \n     \n     //check for lastdays&dep_id parameters\n     if(req.query.lastdays){\n       lastdays=req.query.lastdays\n     }\n     let query={\"id_project\":req.projectid, $or:[ {preflight:false}, { preflight : { $exists: false } } ], \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n     \n     if(req.query.department_id){\n       //department_id=req.query.department_id;\n       //add field departmentid to query if req.query.department_id exist\n       query.department= new ObjectId(req.query.department_id);\n       \n     }\n     \n     winston.debug(\"QueryParams_LastDayCHART:\", lastdays,req.query.department_id)\n     winston.debug(\"Query_LastDayCHART\", query)\n \n     winston.debug(req.params);\n     winston.debug(\"req.projectid\",  req.projectid);    \n    \n     AnalyticResult.aggregate([\n         // { \"$match\": {\"id_project\": req.projectid } },\n         // { \"$match\": {} },\n         { $match: query },\n         { \"$project\":{\n           \"sourcePage\":\"$sourcePage\",            \n         }}, \n         // // Group by year, month and day and get the count\n         { \"$group\":{\n               \"_id\":{\"sourcePage\":\"$sourcePage\"}, \n               \"count\":{\"$sum\":1}\n         }},\n         { \"$sort\": {\"_id\":1}},  \n         // { \"$limit\": 7 },\n     ])\n     // .exec((err, result) => {\n       .exec(function(err, result) {\n \n      \n   //, function (err, result) {\n         if (err) {\n             winston.debug(err);\n             winston.debug(\"ERR\",err)\n             return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n           }\n           winston.debug(result);\n           winston.debug(\"RESULT\",result)\n           res.json(result);\n     });\n \n   });\n\n\n\n\n\n\n\n\n\nrouter.get('/project_users/count', function(req, res) {\n  \n  winston.debug(req.params);\n  winston.debug(\"req.projectid: \"+  req.projectid);    \n \n  AnalyticProject_UsersResult.aggregate([     \n       { \"$match\": {\"id_project\":new ObjectId(req.projectid), role: { $in : [RoleConstants.GUEST, RoleConstants.USER]}, status: \"active\", \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }} },\n      { \"$count\": \"totalCount\" }\n    \n  ])\n    .exec(function(err, result) {   \n      if (err) {\n          winston.debug(err);\n          return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n        }\n        winston.debug(result);\n\n        res.json(result);\n  });\n\n});\n\n\n\n\n  router.get('/project_users/aggregate/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":new ObjectId(req.projectid), role: { $in : [RoleConstants.GUEST, RoleConstants.USER]}, status: \"active\", \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n  \n    \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticProject_UsersResult.aggregate([\n        // { \"$match\": {\"id_project\": req.projectid } },\n        // { \"$match\": {} },\n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"}\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  router.get('/project_users/aggregate/month', function(req, res) {\n    \n    \n    let query={\"id_project\": new ObjectId(req.projectid), role: { $in : [RoleConstants.GUEST, RoleConstants.USER]}, status: \"active\"}\n      \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticProject_UsersResult.aggregate([\n         { \"$match\":  query},\n        // { \"$match\": {} },\n        //{ $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  router.get('/project_users/aggregate/week', function(req, res) {\n          \n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticProject_UsersResult.aggregate([\n         { \"$match\": {\"id_project\": new ObjectId(req.projectid), role: { $in : [RoleConstants.GUEST, RoleConstants.USER]}, status: \"active\" } },\n        // { \"$match\": {} },\n        //{ $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"week\":{\"$week\":\"$createdAt\"},\n          \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\",\"week\":\"$week\", }, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n        // { \"$limit\": 7 },\n    ])\n    // .exec((err, result) => {\n      .exec(function(err, result) {\n\n     \n  //, function (err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  \n\n\n\n\n\n\n\n\n\n\n\n\n\nrouter.get('/messages/count', function(req, res) {\n  \n  winston.debug(req.params);\n  winston.debug(\"req.projectid: \"+  req.projectid);    \n  \n  var query = {\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (30 * 24 * 60 * 60 * 1000))) }};\n\n  if (req.query.sender) {\n    query.sender = req.query.sender;\n  }\n\n  if (req.query.recipient) {\n    query.recipient = req.query.recipient;\n  }\n\n  if (req.query.attributes) {\n    query.attributes = req.query.attributes;\n  }\n\n  if (req.query.attributes_answerid) {\n    query[\"attributes._answerid\"] = req.query.attributes_answerid;\n  }\n\n\n  winston.debug(\"query \", query);    \n    \n  AnalyticMessagesResult.aggregate([     \n      { \"$match\":query},\n      { \"$count\": \"totalCount\" }\n    \n  ])\n    .exec(function(err, result) {   \n      if (err) {\n          winston.debug(err);\n          return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n        }\n        winston.debug(result);\n\n        res.json(result);\n  });\n\n});\n\n\n\n\n  router.get('/messages/aggregate/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n  \n    if (req.query.sender) {\n      query.sender = req.query.sender;\n    }\n  \n    if (req.query.recipient) {\n      query.recipient = req.query.recipient;\n    }\n\n    if (req.query.channel) {\n      winston.debug('req.query.channel', req.query.channel);\n      query['channel.name'] = req.query.channel;\n    }\n\n    \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticMessagesResult.aggregate([       \n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"}\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n    ])\n      .exec(function(err, result) {\n\n     \n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  router.get('/messages/aggregate/month', function(req, res) {\n    \n    \n    let query={\"id_project\": req.projectid}\n     \n    \n    if (req.query.sender) {\n        query.sender = req.query.sender;\n    }\n    \n    if (req.query.recipient) {\n        query.recipient = req.query.recipient;\n    }\n      \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticMessagesResult.aggregate([\n         { \"$match\":  query},\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n    ])\n      .exec(function(err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n  router.get('/messages/aggregate/week', function(req, res) {\n          \n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    let query={\"id_project\": req.projectid}\n      \n    if (req.query.sender) {\n        query.sender = req.query.sender;\n    }\n    \n    if (req.query.recipient) {\n        query.recipient = req.query.recipient;\n    }\n      \n\n    AnalyticMessagesResult.aggregate([\n         { \"$match\":query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"},\n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"week\":{\"$week\":\"$createdAt\"},\n          \n          \n        }}, \n        // // Group by year and month and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\",\"week\":\"$week\", }, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n    ])\n      .exec(function(err, result) {\n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\n\n\n\n  router.get('/messages/aggregate/sender', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n  \n    if (req.query.sender) {\n      query.sender = req.query.sender;\n    }\n  \n    if (req.query.recipient) {\n      query.recipient = req.query.recipient;\n    }\n\n    \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticMessagesResult.aggregate([       \n        { $match: query },\n        { \"$project\":{\n          \"sender\":\"$sender\"          \n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"sender\":\"$sender\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n    ])\n      .exec(function(err, result) {\n\n     \n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n  \n\n\n  router.get('/requests/aggregate/attributes/_answerid', function(req, res) {\n   //set default value for lastdays&department_id \n   let lastdays=7\n   //let department_id='';\n   \n   \n   //check for lastdays&dep_id parameters\n   if(req.query.lastdays){\n     lastdays=req.query.lastdays\n   }\n   let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n \n   if (req.query.sender) {\n     query.sender = req.query.sender;\n   }\n \n   if (req.query.recipient) {\n     query.recipient = req.query.recipient;\n   }\n\n   \n   winston.debug(\"Query_LastDayCHART\", query)\n\n   winston.debug(req.params);\n   winston.debug(\"req.projectid\",  req.projectid);    \n  \n   AnalyticMessagesResult.aggregate([       \n       { $match: query },\n       { \"$project\":{\n        \"_answerid\":\"$attributes._answerid\",            \n      }}, \n      // // Group by year, month and day and get the count\n      { \"$group\":{\n            \"_id\":{\"_answerid\":\"$_answerid\"}, \n            \"count\":{\"$sum\":1}\n      }},\n      { \"$sort\": {\"_id\":1}},  \n   ])\n     .exec(function(err, result) {\n\n    \n       if (err) {\n           winston.debug(err);\n           winston.debug(\"ERR\",err)\n           return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n         }\n         winston.debug(result);\n         winston.debug(\"RESULT\",result)\n         res.json(result);\n   });\n\n });\n\n\n\n  \n\n\n\n\n\n\n router.get('/events/aggregate/day', function(req, res) {\n    \n    //set default value for lastdays&department_id \n    let lastdays=7\n    //let department_id='';\n    \n    \n    //check for lastdays&dep_id parameters\n    if(req.query.lastdays){\n      lastdays=req.query.lastdays\n    }\n    let query={\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (lastdays * 24 * 60 * 60 * 1000))) }}\n  \n    if (req.query.name) {\n      query.name = req.query.name;\n    } else {\n        query.name =  {$ne : \"typing.start\"};\n    }\n\n\n    \n    winston.debug(\"Query_LastDayCHART\", query)\n\n    winston.debug(req.params);\n    winston.debug(\"req.projectid\",  req.projectid);    \n   \n    AnalyticEventsResult.aggregate([       \n        { $match: query },\n        { \"$project\":{\n          \"year\":{\"$year\":\"$createdAt\"}, \n          \"month\":{\"$month\":\"$createdAt\"}, \n          \"day\": {\"$dayOfMonth\":\"$createdAt\"},\n          \"name\": \"$name\"\n        }}, \n        // // Group by year, month and day and get the count\n        { \"$group\":{\n              \"_id\":{\"year\":\"$year\", \"month\":\"$month\", \"day\":\"$day\",\"name\":\"$name\"}, \n              \"count\":{\"$sum\":1}\n        }},\n        { \"$sort\": {\"_id\":1}},  \n    ])\n      .exec(function(err, result) {\n\n     \n        if (err) {\n            winston.debug(err);\n            winston.debug(\"ERR\",err)\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          winston.debug(result);\n          winston.debug(\"RESULT\",result)\n          res.json(result);\n    });\n\n  });\n\nrouter.get('/tags/:type', async (req, res) => {\n\n  let id_project = req.projectid;\n  let type = req.params.type;\n  let startDate = req.query.start_date;\n  let endDate = req.query.end_date;\n\n  if (!type) {\n    return res.status(400).send({ success: false, error: \"Missing parameter 'type'\" });\n  }\n\n  if (!startDate) {\n    startDate = new Date();\n    startDate.setDate(startDate.getDate() - 6);\n  } else {\n    startDate = new Date(startDate)\n  }\n\n  if (!endDate) {\n    endDate = new Date();\n  } else {\n    endDate = new Date(endDate);\n  }\n  \n  startDate = startDate.setHours(0, 0, 0, 0);\n  endDate = endDate.setHours(0, 0, 0, 0);\n\n  if (startDate > endDate) {\n    return res.status(400).send({ success: false, error: \"Invalid dates: start_date can't be greater of end_date\"})\n  }\n\n  let query = {\n    id_project: id_project,\n    type: type,\n    date: { $gte: startDate, $lte: endDate }\n  }\n\n  winston.debug(\"analytics tags query: \", query)\n\n  let result = await Analytics.find(query).catch((err) => {\n    winston.error(\"Error finding Analytics: \", err);\n    return res.status(500).send({ success: false, error: \"Unable to find analytics\" })\n  })\n\n  let parsedDates = result\n    .sort((a, b) => parseInt(a.date) - parseInt(b.date))\n    .map(r => {\n      let date = new Date(parseInt(r.date));\n      let day = String(date.getDate()).padStart(2, '0'); // Day with two digit\n      let month = String(date.getMonth() + 1).padStart(2, '0'); // Month with two digit\n      let year = date.getFullYear(); // Complete year\n      \n      return {\n        formatted: `${day}/${month}/${year}`,\n        original: r\n      };\n  });\n  \n  // Extract and sort all unique dates\n  let dates = [...new Set(parsedDates.map(d => d.formatted))].sort();\n  \n  // Extract all unique keys\n  let allKeys = [...new Set(result.flatMap(r => Object.keys(r.keys)))];\n  \n  // Pre-build a map to optimize data access\n  let dataMap = parsedDates.reduce((acc, { formatted, original }) => {\n    acc[formatted] = original.keys;\n    return acc;\n  }, {});\n  \n  // Build the data series\n  let series = allKeys.map(key => {\n    let values = dates.map(date => dataMap[date]?.[key] || 0); // Default a 0 se il tag non è presente\n    return { name: key, values };\n  });\n  \n  // Assembly final result\n  let data = { dates, series };\n  \n\n  return res.status(200).send(data);\n  \n})\n\nmodule.exports = router;\n"
  },
  {
    "path": "pubmodules/analytics/index.js",
    "content": "const analyticsRoute = require(\"./analytics\");\n\nmodule.exports = {analyticsRoute:analyticsRoute};"
  },
  {
    "path": "pubmodules/apps/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst apps = require(\"@tiledesk/tiledesk-apps\");\nconst appsRoute = apps.router;\n\n\nmodule.exports = { listener: listener, appsRoute: appsRoute }\n\n"
  },
  {
    "path": "pubmodules/apps/listener.js",
    "content": "const apps = require(\"@tiledesk/tiledesk-apps\");\nvar winston = require('../../config/winston');\n\nvar config = require('../../config/database'); // get db config file\n\nlet configSecretOrPubicKay = process.env.GLOBAL_SECRET || config.secret;   \n\nvar pKey = process.env.GLOBAL_SECRET_OR_PUB_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n  configSecretOrPubicKay = pKey.replace(/\\\\n/g, '\\n');\n}\n\n// console.log(\"configSecretOrPubicKay\",configSecretOrPubicKay);\n\nclass Listener {\n\n    listen(config) {\n\n        winston.info(\"Apps Listener listen\");\n        \n        if (config.databaseUri) {\n            winston.debug(\"apps config databaseUri: \" + config.databaseUri);\n        }\n        // console.log(\"ACCESS_TOKEN_SECRET\",process.env.APPS_ACCESS_TOKEN_SECRET || configSecretOrPubicKay);\n\n        apps.startApp({\n            ACCESS_TOKEN_SECRET: process.env.APPS_ACCESS_TOKEN_SECRET || configSecretOrPubicKay,\n            MONGODB_URI: process.env.APPS_MONGODB_URI || config.databaseUri,\n            KALEYRA_ENABLED: process.env.KALEYRA_ENABLED || config.kaleyra_enabled,\n            VOICE_ENABLED: process.env.VOICE_ENABLED || false,\n            isCommunity: process.env.COMMUNITY_VERSION || false\n        }, () => {\n            winston.info(\"Tiledesk Apps proxy server succesfully started.\")\n        })\n\n    }\n\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/cache/index.js",
    "content": "const mongooseCachegoose = require(\"./mongoose-cachegoose-fn\");\nmodule.exports = {cachegoose:mongooseCachegoose};"
  },
  {
    "path": "pubmodules/cache/mongoose-cachegoose-fn.js",
    "content": " var requestEvent = require(\"../../event/requestEvent\");   \n var messageEvent = require(\"../../event/messageEvent\");   \n var projectEvent = require(\"../../event/projectEvent\");   \n var botEvent = require(\"../../event/botEvent\");   \n const faqBotEvent = require('../../event/faqBotEvent');\n var departmentEvent = require(\"../../event/departmentEvent\");   \n var authEvent = require(\"../../event/authEvent\");   \n var labelEvent = require(\"../../event/labelEvent\");\n var integrationEvent = require(\"../../event/integrationEvent\");\n\n var triggerEventEmitter = require(\"../trigger/event/triggerEventEmitter\");\n var subscriptionEvent = require(\"../../event/subscriptionEvent\");   \n var roleEvent = require(\"../../event/roleEvent\");   \n\n var winston = require('../../config/winston');\n\n var cachegoose = require('cachegoose');\n\n var cacheUtil = require('../../utils/cacheUtil');\n var RoleConstants = require(\"../../models/roleConstants\");\n\n \n async function del(client, key, callback) {\n    key = \"cacheman:cachegoose-cache:\" + key;\n    winston.debug(\"key: \"+key)\n   \n    // client.del(key, function (err, data) {\n    //     if (err) {\n    //         return callback(error);\n    //     } else {\n    //         winston.info(\"del data: \"+ data)\n\n    //         return callback(null, data);\n    //     }\n    // });\n\n    // client.del(key).then(function(result) {\n    //     winston.info(\"result\", result)\n    //     return callback(null, result);\n    // }).catch(function(error) {\n    //     winston.error(\"here3\", error)\n    //     return callback(error);\n    // });\n\n    var res = await client.del(key)\n    winston.debug(\"result: \"+ res)\n    if (res) {\n        return callback(null, res); \n    } else {\n        var error = \"error\";\n        return callback(error);\n    }\n }\n\n function listen(client) {\n\n    projectEvent.on(\"project.create\", function(project) {\n        setImmediate(() => {\n            var key = \"projects:id:\"+project.id;\n            winston.verbose(\"Creating cache for project.create with key: \" + key);\n            client.set(key, project, cacheUtil.longTTL, (err, reply) => {\n                winston.verbose(\"Created cache for project.create\",{err:err});\n                winston.debug(\"Created cache for project.create reply\",reply);\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = \"projects:query:*\";\n            // winston.verbose(\"Deleting cache for project.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.verbose(\"Deleted cache for project.create\",{err:err});\n            //     winston.debug(\"Deleted cache for project.create\",reply);\n\n            // }); \n        });  \n    });\n\n    projectEvent.on(\"project.update\", function(project) {\n        setImmediate(() => {\n            var key = \"projects:id:\"+project.id;\n            winston.verbose(\"Updating cache for project.update with key: \" + key);\n            client.set(key, project, cacheUtil.longTTL, (err, reply) => {\n                winston.verbose(\"Updated cache for project.update\",{err:err});\n                winston.debug(\"Updated cache for project.update\",reply);\n\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = \"projects:query:*\";\n            // winston.verbose(\"Deleting cache for project.update with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for project.update\",reply);\n            //     winston.verbose(\"Deleted cache for project.update\",{err:err});\n            // });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for project.update\");\n            invalidateWidgets(client, project.id); //tested\n        });\n\n    });\n\n    projectEvent.on(\"project.delete\", function(project) {\n        setImmediate(() => {\n            var key = \"projects:id:\"+project.id;\n            winston.verbose(\"Deleting cache for project.delete with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, (err, reply) => {\n                winston.debug(\"Deleted cache for project.delete\",reply);\n                winston.verbose(\"Deleted cache for project.delete\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = \"projects:query:*\";\n            // winston.verbose(\"Deleting cache for project.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for project.create\",reply);\n            //     winston.verbose(\"Deleted cache for project.create\",{err:err});\n            // });   \n        });\n\n        // TODO invalidate widgets here\n        winston.verbose(\"Deleting widgets cache for project.delete\");\n        invalidateWidgets(client, project.id);\n    });\n\n    projectEvent.on(\"project.downgrade\", function(project) {\n        setImmediate(() => {\n            var key = \"projects:id:\"+project.id;\n            winston.verbose(\"Updating cache for project.downgrade with key: \" + key);\n\n            client.set(key, project, cacheUtil.longTTL, (err, reply) => {\n                winston.debug(\"Updated cache for project.downgrade\",reply);\n                winston.verbose(\"Updated cache for project.downgrade\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = \"projects:query:*\";\n            // winston.verbose(\"Deleting cache for project.downgrade with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for project.downgrade\",reply);\n            //     winston.verbose(\"Deleted cache for project.downgrade\",{err:err});\n            // }); \n            \n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for project.downgrade\");\n            invalidateWidgets(client, project.id);\n        });\n    });\n\n\n\n\n    \n\n    authEvent.on('project_user.update', function(data) {\n        setImmediate(() => {\n\n            var project_user = data.updatedProject_userPopulated;\n\n            var key = project_user.id_project+\":project_users:id:\"+project_user.id;\n            winston.verbose(\"Updating cache for project_user.update with key: \" + key);\n            client.set(key, project_user, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Updated cache for project_user.update\",reply);\n                winston.verbose(\"Updated cache for project_user.update\",{err:err});\n            });\n\n            if (project_user.id_user) {\n                var key = project_user.id_project+\":project_users:iduser:\"+project_user.id_user;\n                winston.verbose(\"Updating cache for project_user.update with key: \" + key);\n                client.set(key, project_user, cacheUtil.defaultTTL, (err, reply) => {\n                    winston.debug(\"Updated cache for project_user.update\",reply);\n                    winston.verbose(\"Updated cache for project_user.update\",{err:err});\n                });\n            }\n\n            if (project_user.uuid_user) {\n                var key = project_user.id_project+\":project_users:uuid_user:\"+project_user.uuid_user;\n                winston.verbose(\"Updating cache for project_user.update with key: \" + key);\n                client.set(key, project_user, cacheUtil.defaultTTL, (err, reply) => {\n                    winston.debug(\"Updated cache for project_user.update\",reply);\n                    winston.verbose(\"Updated cache for project_user.update\",{err:err});\n                });\n            }\n            \n\n\n            var role = project_user.role;\n\n            var TEAMMATE_ROLES =  {       \n                \"agent\": [\"guest\",\"user\",\"agent\"],\n                \"admin\": [\"guest\",\"user\",\"agent\", \"admin\",],\n                \"owner\": [\"guest\",\"user\",\"agent\", \"admin\", \"owner\"],\n            }\n            // controllare bene\n\n            var hierarchicalRoles = TEAMMATE_ROLES[role];\n            winston.debug(\"hierarchicalRoles\", hierarchicalRoles);\n        \n            if ( hierarchicalRoles && hierarchicalRoles.includes(role)) {\n\n                var key = project_user.id_project+\":project_users:role:teammate:\"+project_user.id;\n                winston.verbose(\"Updating cache for project_user.update with key: \" + key);\n                client.set(key, project_user, cacheUtil.defaultTTL, (err, reply) => {\n                    winston.debug(\"Updated cache for project_user.update\",reply);\n                    winston.verbose(\"Updated cache for project_user.update\",{err:err});\n                });\n            }\n\n\n            \n            // TODO invalidate widgets headers\n            // only if role is agent, owner, admin ATTENTION\n            if (role == RoleConstants.OWNER || role == RoleConstants.ADMIN || role == RoleConstants.SUPERVISOR || role == RoleConstants.AGENT) {\n                winston.verbose(\"Deleting widgets cache for project_user.update\");\n                invalidateWidgets(client, project_user.id_project);    //tested\n            }else {\n                winston.verbose(\"NOT invalidating widget cache for non admins project_user role\");//tested\n            }\n            \n        });\n    });\n   \n\n\n\n\n    authEvent.on('user.signup', function(data) {\n        setImmediate(() => {\n            var user = data.savedUser;\n\n            var key = \"users:id:\"+user.id;\n            winston.verbose(\"Creating cache for user.signup with key: \" + key);\n            client.set(key, user, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for user.signup\",reply);\n                winston.verbose(\"Created cache for user.signup\",{err:err});\n            });\n\n            // NOT IN USE (TESTED)\n            // var key = \"users:email:\"+user.email;\n            // winston.verbose(\"Creating cache for user.signup with key: \" + key);\n            // client.set(key, user, cacheUtil.defaultTTL, (err, reply) => {\n            //     winston.debug(\"Created cache for user.signup\",reply);\n            //     winston.verbose(\"Created cache for user.signup\",{err:err});\n            // });\n        });\n    });\n\n\n    authEvent.on('user.update', function(data) {\n        setImmediate(() => {\n            var user = data.updatedUser;\n\n            var key = \"users:id:\"+user.id;\n            winston.verbose(\"Updating cache for user.update with key: \" + key);\n            client.set(key, user, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Updated cache for user.update\",reply);\n                winston.verbose(\"Updated cache for user.update\",{err:err});\n            });\n\n            // NOT IN USE (TESTED)\n            // var key = \"users:email:\"+user.email;\n            // winston.verbose(\"Updating cache for user.update with key: \" + key);\n            // client.set(key, user, cacheUtil.defaultTTL, (err, reply) => {\n            //     winston.debug(\"Updated cache for user.update\",reply);\n            //     winston.verbose(\"Updated cache for user.update\",{err:err});\n            // });\n        });\n    });\n   \n    authEvent.on('user.delete', function(data) {\n        setImmediate(() => {\n            var user = data.user;\n\n            var key = \"users:id:\"+user.id;\n            winston.verbose(\"Deleting cache for user.delete with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, (err, reply) => {\n                winston.debug(\"Deleted cache for user.delete\",reply);\n                winston.verbose(\"Deleted cache for user.delete\",{err:err});\n            });\n\n            // NOT IN USE (TESTED)\n            // var key = \"users:email:\"+user.email;\n            // winston.verbose(\"Deleting cache for user.delete with key: \" + key);\n            // client.del(key, (err, reply) => {\n            //     winston.debug(\"Deleted cache for user.delete\",reply);\n            //     winston.verbose(\"Deleted cache for user.delete\",{err:err});\n            // });\n        });\n    });\n\n\n\n\n    requestEvent.on(\"request.create.simple\", function(request) {\n        setImmediate(() => {\n            var key = request.id_project+\":requests:id:\"+request.id+\":simple\";\n            winston.verbose(\"Creating cache for request.create.simple with key: \" + key);\n\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.create.simple\",reply);\n                winston.verbose(\"Created cache for request.create.simple\",{err:err});\n            });\n\n\n            var key = \"requests:request_id:\"+request.request_id+\":simple\";  //without project for chat21 webhook\n            winston.verbose(\"Creating cache for request.create.simple without project with key : \" + key);\n\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.create.simple\",reply);\n                winston.verbose(\"Created cache for request.create.simple\",{err:err});\n            });\n\n            var key = request.id_project+\":requests:request_id:\"+request.request_id+\":simple\";\n            winston.verbose(\"Creating cache for request.create.simple with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.create.simple\",reply);\n                winston.verbose(\"Created cache for request.create.simple\",{err:err});\n            });\n\n        })\n    });\n\n    function invalidatRequestSimple(client, project_id, rid, request_id) {\n        var key = project_id+\":requests:id:\"+rid+\":simple\";\n        winston.verbose(\"Deleting cache for widgets with key: \" + key);\n\n        // found del\n        winston.debug(\"Deleted4545\");\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        \n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for invalidatRequestSimple\",reply);\n            winston.verbose(\"Deleted cache for invalidatRequestSimple\",{err:err});\n        });   \n\n        key = \"requests:request_id:\"+request_id+\":simple\";  //without project for chat21 webhook\n        winston.verbose(\"Creating cache for request.create.simple for invalidatRequestSimple without project with key : \" + key);\n        // found del\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for invalidatRequestSimple\",reply);\n            winston.verbose(\"Deleted cache for invalidatRequestSimple\",{err:err});\n        });   \n\n        key = project_id+\":requests:request_id:\"+request_id+\":simple\";\n        winston.verbose(\"Creating cache for request.create.simple for invalidatRequestSimple with key: \" + key);\n        // found del\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for invalidatRequestSimple\",reply);\n            winston.verbose(\"Deleted cache for invalidatRequestSimple\",{err:err});\n        });   \n\n\n    }\n\n\n    requestEvent.on(\"request.create\", function(request) {\n        setImmediate(() => {\n            var key = request.id_project+\":requests:id:\"+request.id;\n            winston.verbose(\"Creating cache for request.create with key: \" + key);\n\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.create\",reply);\n                winston.verbose(\"Created cache for request.create\",{err:err});\n            });\n\n            var key = request.id_project+\":requests:request_id:\"+request.request_id;\n            winston.verbose(\"Creating cache for request.create with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.create\",reply);\n                winston.verbose(\"Created cache for request.create\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = request.id_project+\":requests:query:*\";\n            // winston.verbose(\"Deleting cache for request.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for request.create\",reply);\n            //     winston.verbose(\"Deleted cache for request.create\",{err:err});\n            // });   \n\n        });\n    });\n\n\n\n    requestEvent.on(\"request.update\", function(request) {  \n        setImmediate(() => {\n            var key = request.id_project+\":requests:id:\"+request.id;\n            winston.verbose(\"Creating cache for request.update with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.update\",reply);\n                winston.verbose(\"Created cache for request.update\",{err:err});\n            });\n\n            var key = request.id_project+\":requests:request_id:\"+request.request_id;\n            winston.verbose(\"Creating cache for request.update with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.update\",reply);\n                winston.verbose(\"Created cache for request.update\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = request.id_project+\":requests:query:*\";\n            // winston.verbose(\"Deleting cache for request.update with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for request.update\",reply);\n            //     winston.verbose(\"Deleted cache for request.update\",{err:err});\n            // });   \n\n            invalidatRequestSimple(client, request.id_project, request.id, request.request_id)\n\n        });\n    });\n\n\n    requestEvent.on(\"request.close\", function(request) { \n        setImmediate(() => {\n            var key = request.id_project+\":requests:id:\"+request.id;\n            winston.verbose(\"Creating cache for request.close with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.close\",reply);\n                winston.verbose(\"Created cache for request.close\",{err:err});\n            });\n\n            var key = request.id_project+\":requests:request_id:\"+request.request_id;\n            winston.verbose(\"Creating cache for request.close with key: \" + key);\n            client.set(key, request, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for request.close\",reply);\n                winston.verbose(\"Created cache for request.close\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = request.id_project+\":requests:query:*\";\n            // winston.verbose(\"Deleting cache for request.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for request.close\",reply);\n            //     winston.verbose(\"Deleted cache for request.close\",{err:err});\n            // });   \n\n            invalidatRequestSimple(client, request.id_project, request.id, request.request_id)\n\n        });\n    });\n\n\n\n\n// non serve tanto\n    // messageEvent.on(\"message.create\", function(message) { \n    //     setImmediate(() => {\n    //         var key = message.id_project+\":requests:id:\"+message.request._id + \":messages:id:\" + message._id;\n    //         winston.verbose(\"Creating cache for message.create with key: \" + key);\n    //         client.set(key, message, cacheUtil.defaultTTL, (err, reply) => {\n    //             winston.verbose(\"Created cache for message.create\",{err:err, reply:reply});\n    //         });\n\n    //         var key = message.id_project+\":requests:request_id:\"+message.request.request_id + \":messages:id:\" + message._id;        \n    //         winston.verbose(\"Creating cache for message.create with key: \" + key);\n    //         client.set(key, message, cacheUtil.defaultTTL, (err, reply) => {\n    //             winston.verbose(\"Created cache for message.create\",{err:err, reply:reply});\n    //         });\n    //     });\n    // });\n\n\n\n    botEvent.on(\"faqbot.create\", function(faq_kb) {\n        setImmediate(() => {\n            let clonedbot = Object.assign({}, faq_kb);\n            delete clonedbot.secret;\n\n            var key = faq_kb.id_project+\":faq_kbs:id:\"+faq_kb._id;\n            winston.verbose(\"Creating cache for faq_kb.create with key: \" + key);\n            client.set(key, clonedbot, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for faq_kb.create\",reply);\n                winston.verbose(\"Created cache for faq_kb.create\",{err:err});\n            });\n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faqbot.create\");\n            invalidateWidgets(client, faq_kb.id_project); //tested\n        });\n    });\n\n\n\n    botEvent.on(\"faqbot.update\", function(faq_kb) { \n        setImmediate(() => {\n            var key = faq_kb.id_project+\":faq_kbs:id:\"+faq_kb._id;\n            winston.verbose(\"Creating cache for faq_kb.update with key: \" + key);\n            client.set(key, faq_kb, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for faq_kb.create\",reply);\n                winston.verbose(\"Created cache for faq_kb.update\",{err:err});\n            }); \n            \n\n            key = faq_kb.id_project+\":faq_kbs:id:\"+faq_kb._id+\":secret\";\n            winston.verbose(\"Deleting cache for faq_kb.update secret with key: \" + key);\n            // found del\n            // winston.info(\"quiqui\", client._cache);\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, function (err, reply) {  //tested\n                winston.debug(\"Deleted cache for faq_kb.update secret\",reply);\n                winston.verbose(\"Deleted cache for faq_kb.update secret\",{err:err});\n            });   \n\n            // without project for tilebot\n            key = \"faq_kbs:id:\"+faq_kb._id;\n            winston.verbose(\"Deleting cache for faq_kb.update without project for tilebot with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, function (err, reply) {  \n                winston.debug(\"Deleted cache for faq_kb.update  without project for tilebot \",reply);\n                winston.verbose(\"Deleted cache for faq_kb.update  without project for tilebot \",{err:err});\n            });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faqbot.update\");\n            invalidateWidgets(client, faq_kb.id_project); //TESTED\n        });\n    });\n\n\n    botEvent.on(\"faqbot.delete\", function(faq_kb) {   //LOGIC deletion for chatbot is used\n        setImmediate(() => {\n            var key = faq_kb.id_project+\":faq_kbs:id:\"+faq_kb._id;\n            winston.verbose(\"Deleting cache for faqbot.delete with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, (err, reply) => {\n                winston.debug(\"Deleted cache for faqbot.delete\",reply);\n                winston.verbose(\"Deleted cache for faqbot.delete\",{err:err});\n            });\n\n\n            key = faq_kb.id_project+\":faq_kbs:id:\"+faq_kb._id+\":secret\";\n            winston.verbose(\"Deleting cache for faq_kb.delete secret with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, function (err, reply) {\n                winston.debug(\"Deleted cache for faq_kb.delete secret\",reply);\n                winston.verbose(\"Deleted cache for faq_kb.delete secret\",{err:err});\n            });  \n            \n\n            // without project for tilebot\n            key = \"faq_kbs:id:\"+faq_kb._id;\n            winston.verbose(\"Deleting cache for faq_kb.update without project for tilebot with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, function (err, reply) {  \n                winston.debug(\"Deleted cache for faq_kb.update  without project for tilebot \",reply);\n                winston.verbose(\"Deleted cache for faq_kb.update  without project for tilebot \",{err:err});\n            });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faqbot.delete\");\n            invalidateWidgets(client, faq_kb.id_project); //tested\n        });\n    });\n\n\n\n    faqBotEvent.on(\"faq.create\", function(faq) { \n        setImmediate(() => {            \n            \n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faq.create\");\n            invalidateWidgets(client, faq.id_project); //tested\n        });\n    });\n\n    faqBotEvent.on(\"faq.update\", function(faq) { \n        setImmediate(() => {            \n            invalidateFaq(client, faq);\n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faq.update\");\n            invalidateWidgets(client, faq.id_project);//tested\n        });\n    });\n\n    faqBotEvent.on(\"faq.delete\", function(faq) { \n        setImmediate(() => {            \n            invalidateFaq(client, faq);           \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for faq.delete\",faq);\n            invalidateWidgets(client, faq.id_project);//tested\n        });\n    });\n\n\n    departmentEvent.on(\"department.create\", function(department) {\n        setImmediate(() => {\n            var key = department.id_project+\":departments:id:\"+department._id;\n            winston.verbose(\"Creating cache for department.create with key: \" + key);\n            client.set(key, department, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for department.create\",reply);\n                winston.verbose(\"Created cache for department.create\",{err:err});\n            });\n            \n            // TODO COMMENTA NON USATO\n            // key = department.id_project+\":departments:query:*\";        \n            // winston.verbose(\"Deleting cache for department.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for department.create\",reply);\n            //     winston.verbose(\"Deleted cache for department.create\",{err:err});\n            // });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for department.create\");\n            invalidateWidgets(client, department.id_project); \n        });\n    });\n\n\n\n    departmentEvent.on(\"department.update\", function(department) {  \n        setImmediate(() => {\n            var key = department.id_project+\":departments:id:\"+department._id;\n            winston.verbose(\"Creating cache for department.update with key: \" + key);\n            client.set(key, department, cacheUtil.defaultTTL, (err, reply) => {\n                winston.debug(\"Created cache for department.update\",reply);\n                winston.verbose(\"Created cache for department.update\",{err:err});\n            });    \n\n            // TODO COMMENTA NON USATO\n            // key = department.id_project+\":departments:query:*\";        \n            // winston.verbose(\"Deleting cache for department.update with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for department.update\",reply);\n            //     winston.verbose(\"Deleted cache for department.update\",{err:err});\n            // });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for department.update\");\n            invalidateWidgets(client, department.id_project); //tested\n        });\n    });\n\n\n    departmentEvent.on(\"department.delete\", function(department) { \n        setImmediate(() => {\n            var key = department.id_project+\":departments:id:\"+department._id;\n            winston.verbose(\"Deleting cache for department.delete with key: \" + key);\n            // found del\n            del(client._cache._engine.client, key, function (err, reply) {  //tested\n            // client.del(key, (err, reply) => {\n                winston.debug(\"Deleted cache for department.delete\",reply);\n                winston.verbose(\"Deleted cache for department.delete\",{err:err});\n            });\n\n            // TODO COMMENTA NON USATO\n            // key = department.id_project+\":departments:query:*\";        \n            // winston.verbose(\"Deleting cache for department.delete with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for department.delete\",reply);\n            //     winston.verbose(\"Deleted cache for department.delete\",{err:err});\n            // });   \n\n            // TODO invalidate widgets here\n            winston.verbose(\"Deleting widgets cache for department.delete\");\n            invalidateWidgets(client, department.id_project);\n        });\n    });\n\n\n    labelEvent.on(\"label.create\", function(label) { \n        setImmediate(() => {    \n\n            // TODO COMMENTA NON USATO\n            // var key = label.id_project+\":labels:query:*\";        \n            // winston.verbose(\"Deleting cache for label.create with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for label.create\",reply);\n            //     winston.verbose(\"Deleted cache for label.create\",{err:err});\n            // });   \n        });\n    });\n\n\n\n    labelEvent.on(\"label.update\", function(label) {        \n        setImmediate(() => {    \n\n            // TODO COMMENTA NON USATO\n            // var key = label.id_project+\":labels:query:*\";        \n            // winston.verbose(\"Deleting cache for label.update with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for label.update\",reply);\n            //     winston.verbose(\"Deleted cache for label.update\",{err:err});\n            // });   \n        });\n    });\n\n\n    labelEvent.on(\"label.clone\", function(label) {    \n        setImmediate(() => {       \n            \n            // TODO COMMENTA NON USATO\n            // var key = label.id_project+\":labels:query:*\";        \n            // winston.verbose(\"Deleting cache for label.clone with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for label.clone\",reply);\n            //     winston.verbose(\"Deleted cache for label.clone\",{err:err});\n            // });   \n        });\n    });\n\n\n    labelEvent.on(\"label.delete\", function(label) {     \n        setImmediate(() => {         \n\n            // TODO COMMENTA NON USATO\n            // var key = label.id_project+\":labels:query:*\";        \n            // winston.verbose(\"Deleting cache for label.delete with key: \" + key);\n            // client.del(key, function (err, reply) {\n            //     winston.debug(\"Deleted cache for label.delete\",reply);\n            //     winston.verbose(\"Deleted cache for label.delete\",{err:err});\n            // });   \n        });\n    });\n\n    // l'evento è relativo a tutte le integrazioni, è sufficiente solo un evento\n    // per create, update, delete di una singola creation\n    integrationEvent.on(\"integration.update\", (integrations, id_project) => {\n        let key = \"project:\" + id_project + \":integrations\";\n        winston.verbose(\"Creating cache for integration.create with key: \" + key);\n        client.set(key, integrations, cacheUtil.longTTL, (err, reply) => {\n            winston.verbose(\"Created cache for integration.create\", {err: err} );\n            winston.debug(\"Created cache for integration.create reply\", reply);\n        })\n    })\n\n    // fai cache per subscription.create, .update .delete\n\n\n    if (subscriptionEvent) {\n        subscriptionEvent.on('subscription.create', function(subscription) {   \n            setImmediate(() => {    \n                \n                //TODO i think you must clone trigger and remove .secret before caching\n                // q.cache(cacheUtil.longTTL, id_project+\":subscriptions:event:\"+event);  //CACHE_SUBSCRIPTION\n                var key =subscription.id_project+\":subscriptions:event:\"+subscription.event;    \n\n                // var key =trigger.id_project+\":subscriptions:*\";        //wildcardcache //tested\n                winston.verbose(\"Deleting cache for subscription.create with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for subscription.create\",reply);\n                    winston.verbose(\"Deleted cache for subscription.create\",{err:err});\n                });   \n            });\n        });\n    \n        subscriptionEvent.on('subscription.update', function(subscription) {   \n            setImmediate(() => {         \n                var key =subscription.id_project+\":subscriptions:event:\"+subscription.event;    \n\n                // var key =subscription.id_project+\":subscriptions:*\";         //wildcardcache //tested\n                winston.verbose(\"Deleting cache for subscription.update with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for subscription.update\",reply);\n                    winston.verbose(\"Deleted cache for subscription.update\",{err:err});\n                });   \n            });\n        });\n    \n        subscriptionEvent.on(\"subscription.delete\", function(subscription) {     \n            setImmediate(() => {         \n                var key =subscription.id_project+\":subscriptions:event:\"+subscription.event;    \n\n                // var key =subscription.id_project+\":subscriptions:*\";          //wildcardcache //tested\n                winston.verbose(\"Deleting cache for subscription.delete with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for subscription.delete\",reply);\n                    winston.verbose(\"Deleted cache for subscription.delete\",{err:err});\n                });   \n            });\n        });\n    \n    }\n\n\n    if (triggerEventEmitter) {\n        triggerEventEmitter.on('trigger.create', function(trigger) {   \n            setImmediate(() => {    \n                var key =trigger.id_project+\":triggers:trigger.key:\"+trigger.trigger.key;\n\n                // var key =trigger.id_project+\":triggers:*\";         //wildcardcache //tested\n                winston.verbose(\"Deleting cache for trigger.create with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for trigger.create\",reply);\n                    winston.verbose(\"Deleted cache for trigger.create\",{err:err});\n                });   \n            });\n        });\n    \n        triggerEventEmitter.on('trigger.update', function(trigger) {   \n            setImmediate(() => {         \n                var key =trigger.id_project+\":triggers:trigger.key:\"+trigger.trigger.key;\n                // var key =trigger.id_project+\":triggers:*\";         //wildcardcache //tested\n\n                winston.verbose(\"Deleting cache for trigger.update with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for trigger.update\",reply);\n                    winston.verbose(\"Deleted cache for trigger.update\",{err:err});\n                });   \n            });\n        });\n    \n        triggerEventEmitter.on(\"trigger.delete\", function(trigger) {     \n            setImmediate(() => {      \n                var key =trigger.id_project+\":triggers:trigger.key:\"+trigger.trigger.key;\n\n                // var key =trigger.id_project+\":triggers:*\";          //wildcardcache\n                winston.verbose(\"Deleting cache for trigger.delete with key: \" + key);\n                // found del\n                del(client._cache._engine.client, key, function (err, reply) {  //tested\n                // client.del(key, function (err, reply) {\n                    winston.debug(\"Deleted cache for trigger.delete\",reply);\n                    winston.verbose(\"Deleted cache for trigger.delete\",{err:err});\n                });   \n            });\n        });\n\n\n        roleEvent.on('role.create', function(role) {   \n            setImmediate(() => {    \n                var key =role.id_project+\":roles:\"+role.name;\n                winston.verbose(\"Deleting cache for role.create with key: \" + key);\n                winston.verbose(\"Creating cache for department.create with key: \" + key);\n                client.set(key, role, cacheUtil.defaultTTL, (err, reply) => {\n                    winston.debug(\"Created cache for role.create\",reply);\n                    winston.verbose(\"Created cache for role.create\",{err:err});\n                });\n            \n            });\n        });\n\n         roleEvent.on('role.update', function(role) {   \n            setImmediate(() => {    \n                var key =role.id_project+\":roles:\"+role.name;\n                winston.verbose(\"Deleting cache for role.update with key: \" + key);\n                client.set(key, role, cacheUtil.defaultTTL, (err, reply) => {\n                    winston.debug(\"Updated cache for role.update\",reply);\n                    winston.verbose(\"Updated cache for role.update\",{err:err});\n                });\n            });\n        });\n    \n         roleEvent.on(\"role.delete\", function(role) {     \n            setImmediate(() => {      \n                var key =role.id_project+\":roles:\"+role.name;\n                winston.verbose(\"Deleting cache for role.delete with key: \" + key);\n                del(client._cache._engine.client, key, function (err, reply) {  \n                    winston.debug(\"Deleted cache for role.delete\",reply);\n                    winston.verbose(\"Deleted cache for role.delete\",{err:err});\n                });   \n            });\n        });\n        \n    }\n    \n\n    function invalidateFaq(client, faq) {\n\n        // let faqCacheKey = \"cacheman:cachegoose-cache:faqs:botid:\"+ botId + \":faq:id:\" + key;\n                            //cacheman:cachegoose-cache:faqs:botid:647dc3e8da041c001396f764:faq:id:647dc3ea61ad510014ca0b46\n        key = \"faqs:botid:\"+faq.id_faq_kb+\":faq:id:\"+faq.intent_display_name;\n        // key = \"faqs:botid:\"+faq.id_faq_kb+\":faq:id:\"+faq._id;\n        // key = \"faqs:botid:\"+faq.id_faq_kb+\":faq:id:*\";\n        winston.verbose(\"Deleting cache for faq with key: \" + key);\n        // found del\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for faq\",reply);\n            winston.verbose(\"Deleted cache for faq\",{err:err});\n        });   \n\n\n\n        key = \"faqs:botid:\"+faq.id_faq_kb+\":faq:id:#\"+faq.intent_id;       \n        winston.verbose(\"Deleting cache for faq with key: \" + key);\n        // found del\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for faq\",reply);\n            winston.verbose(\"Deleted cache for faq\",{err:err});\n        });   \n        \n    }\n\n    function invalidateWidgets(client, project_id) {\n        let key = project_id+\":widgets\";\n        winston.verbose(\"Deleting cache for widgets with key: \" + key);\n        // found del\n        del(client._cache._engine.client, key, function (err, reply) {  //tested\n        // client.del(key, function (err, reply) {\n            winston.debug(\"Deleted cache for widgets\",reply);\n            winston.verbose(\"Deleted cache for widgets\",{err:err});\n        });   \n    }\n\n\n    //jwt\n\n    // fai cache faq\n\n }\n\nmodule.exports = function (mongoose, option) {\n\n    if (process.env.CACHE_ENABLED == true || process.env.CACHE_ENABLED == \"true\") {\n        var engine = process.env.CACHE_ENGINE;\n        winston.debug(\"Redis engine: \"+ engine);\n\n        // var endPoint = process.env.CACHE_REDIS_ENDPOINT || \"redis://127.0.0.1:6379\";\n        // winston.debug(\"Redis endpoint: \"+ endPoint);\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \"+ port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\"\n        winston.debug(\"Redis host: \"+ host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \"+ password);\n        \n        winston.info(\"Mongoose Cachegoose fn initialized, engine: \" + engine + \", port: \"+ port + \", host: \"+ host  + \" defaultTTL: \" +cacheUtil.defaultTTL + \", password: \"+ password);\n        // winston.info(\"Mongoose Cachegoose fn initialized, engine: \" + engine + \", endpoint: \"+endPoint +\", port: \"+ port + \", host: \"+ host + \", password: \"+ password);\n\n\n\n        \n        // cachegoose(mongoose, endPoint);\n        \n        cachegoose(mongoose, {\n            engine: engine,    /* If you don't specify the redis engine,      */\n            port: port,         /* the query results will be cached in memory. */            \n            host: host,\n            password: password           \n          });\n\n        var client = cachegoose._cache;\n        // winston.info(\"client client\", client);\n        // winston.info(\"client _cache._engine\", client._cache._engine);\n        // winston.info(\"client _cache._engine.client\", client._cache._engine.client);\n        listen(client);\n\n        return cachegoose;\n    }else {\n        winston.info(\"Mongoose Cachegoose disabled\");\n\n        return;\n    }\n\n    // console.log(\"init\",init);  \n    // console.log(\"cachegoose._cache\",cachegoose._cache);  \n    // cachegoose._cache.get(\"/projects/5ea800091147f28c72b90c5e\", (err, cachedResults) => { //\n    //     console.log(\"cachedResults\",cachedResults);  \n    // });\n\n  \n}"
  },
  {
    "path": "pubmodules/canned/cannedResponse.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../../config/winston');\n\n\nvar CannedResponseSchema = new Schema({\n  \n  title: {\n    type: String,\n    required: false,\n    index: true\n  },\n  text: {\n    type: String,\n    required: true,\n  },\n  shared: {\n    type: Boolean,\n    required: true\n  },\n  attributes: {\n    type: Object,\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  status: {\n    type: Number,\n    required: false,\n    default: 100,\n    index: true\n  }, \n},{\n  timestamps: true\n}\n);\n\n\n// CannedResponseSchema.index({text: 'text'},\n//  {\"name\":\"cannedresponse_fulltext\",\"default_language\": \"italian\",\"language_override\": \"dummy\"}); // schema level\n\n var CannedResponse = mongoose.model('cannedResponse', CannedResponseSchema);\n\n if (process.env.MONGOOSE_SYNCINDEX) {\n  CannedResponse.syncIndexes();\n  winston.info(\"CannedResponse syncIndexes\")\n}\n\nmodule.exports = CannedResponse;\n"
  },
  {
    "path": "pubmodules/canned/cannedResponseRoute.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar CannedResponse = require(\"./cannedResponse\");\nvar winston = require('../../config/winston');\nconst RoleConstants = require('../../models/roleConstants');\nconst roleConstants = require('../../models/roleConstants');\n// const CannedResponseEvent = require('../event/CannedResponseEvent');\n\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var newCannedResponse = new CannedResponse({\n    title: req.body.title,  \n    text: req.body.text,\n    id_project: req.projectid,\n    createdBy: req.user.id,\n    updatedBy: req.user.id,\n    shared: false\n  });\n\n  if (req.projectuser.role == 'owner' || req.projectuser.role == 'admin') {\n    newCannedResponse.shared = true;\n  } else {\n    if (req.projectuser.roleType === roleConstants.TYPE_AGENTS) {\n      if (req.body.shared && req.body.shared === true) {\n        newCannedResponse.shared = true;\n      }\n    }\n  }\n\n  newCannedResponse.save(function (err, savedCannedResponse) {\n    if (err) {\n      winston.error('--- > ERROR ', err)\n\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n\n    res.json(savedCannedResponse);\n  });\n});\n\nrouter.put('/:cannedResponseid', async function (req, res) {\n  winston.debug(req.body);\n  const canned_id = req.params.cannedResponseid;\n  const id_project = req.projectid;\n  let user_role = req.projectuser?.role;\n  let roleType = req.projectuser?.roleType || null;\n  var update = {};\n\n  const allowedFields = ['title', 'text', 'attributes']\n\n  allowedFields.forEach(f => {\n    if (req.body[f] !== undefined) {\n      update[f] = req.body[f];\n    }\n  })\n  \n  let canned = await CannedResponse.findOne({ _id: canned_id, id_project: id_project }).catch((err) => {\n    winston.error(\"Error finding canned response: \", err);\n    return res.status(500).send({ success: false, error: \"General error: cannot find the canned response with id \" + canned_id })\n  })\n\n  if (!canned) {\n    winston.verbose(\"Canned response with id \" + canned_id + \" not found.\");\n    return res.status(404).send({ success: false, error: \"Canned response not found with id \" + canned_id + \" for project \" + id_project })\n  }\n\n  /**\n   * Change type from mongoose object to javascript standard object.\n   * Otherwise hasOwnProperty wouldn't works.\n   */\n  canned = canned.toObject();\n\n  if (user_role === RoleConstants.AGENT) {\n    if (canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't modify a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"You are not allowed to modify a canned response that is not yours.\"})\n    }\n  }\n  else if (user_role === RoleConstants.OWNER || user_role === RoleConstants.ADMIN) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't modify a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to modify a non administration canned response\"})\n    }\n  }\n  else if (roleType === RoleConstants.TYPE_AGENTS) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false && canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't modify a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to modify a non administration canned response\"})\n    }\n  } else {\n    winston.warn(\"User \" + req.user.id + \"trying to modify canned with role \" + user_role);\n    return res.status(401).send({ success: false, error: \"Unauthorized\"})\n  }\n  \n  CannedResponse.findByIdAndUpdate(canned_id, update, { new: true, upsert: true }, function (err, updatedCannedResponse) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    // CannedResponseEvent.emit('CannedResponse.update', updatedCannedResponse);\n    res.json(updatedCannedResponse);\n  });\n});\n\nrouter.delete('/:cannedResponseid', async function (req, res) {\n  winston.debug(req.body);\n  const canned_id = req.params.cannedResponseid;\n  const id_project = req.projectid;\n  let user_role = req.projectuser.role;\n  let roleType = req.projectuser?.roleType || null;\n\n  let canned = await CannedResponse.findOne({ _id: canned_id, id_project: id_project }).catch((err) => {\n    winston.error(\"Error finding canned response: \", err);\n    return res.status(500).send({ success: false, error: \"General error: cannot find the canned response with id \" + canned_id })\n  })\n\n  if (!canned) {\n    winston.verbose(\"Canned response with id \" + canned_id + \" not found.\");\n    return res.status(404).send({ success: false, error: \"Canned response not found with id \" + canned_id + \" for project \" + id_project })\n  }\n\n  /**\n   * Change type from mongoose object to javascript standard object.\n   * Otherwise hasOwnProperty wouldn't works.\n   */\n  canned = canned.toObject();\n  \n  if (user_role === RoleConstants.AGENT) {\n    if (canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"You are not allowed to delete a canned response that is not yours.\"})\n    }\n  }\n  else if (user_role === RoleConstants.OWNER || user_role === RoleConstants.ADMIN) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to delete a non administration canned response\"})\n    }\n  } \n  else if (roleType === RoleConstants.TYPE_AGENTS) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false && canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to delete a non administration canned response\"})\n    }\n  }\n  else {\n    winston.warn(\"User \" + req.user.id + \"trying to delete canned with role \" + user_role);\n    return res.status(401).send({ success: false, error: \"Unauthorized\"})\n  }\n\n  CannedResponse.findByIdAndUpdate(canned_id, {status: 1000}, { new: true, upsert: true }, function (err, updatedCannedResponse) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    // CannedResponseEvent.emit('CannedResponse.delete', updatedCannedResponse);\n    res.json(updatedCannedResponse);\n  });\n});\n\nrouter.delete('/:cannedResponseid/physical', async function (req, res) {\n  winston.debug(req.body);\n  const canned_id = req.params.cannedResponseid;\n  const id_project = req.projectid;\n  let user_role = req.projectuser.role;\n  let roleType = req.projectuser?.roleType || null;\n\n  let canned = await CannedResponse.findOne({ _id: canned_id, id_project: id_project }).catch((err) => {\n    winston.error(\"Error finding canned response: \", err);\n    return res.status(500).send({ success: false, error: \"General error: cannot find the canned response with id \" + canned_id })\n  })\n\n  if (!canned) {\n    winston.verbose(\"Canned response with id \" + canned_id + \" not found.\");\n    return res.status(404).send({ success: false, error: \"Canned response not found with id \" + canned_id + \" for project \" + id_project })\n  }\n\n  /**\n   * Change type from mongoose object to javascript standard object.\n   * Otherwise hasOwnProperty wouldn't works.\n   */\n  canned = canned.toObject();\n  \n  if (user_role === RoleConstants.AGENT) {\n    if (canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"You are not allowed to delete a canned response that is not yours.\"})\n    }\n  }\n  else if (user_role === RoleConstants.OWNER || user_role === RoleConstants.ADMIN) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to delete a non administration canned response\"})\n    }\n  } \n  else if (roleType === RoleConstants.TYPE_AGENTS) {\n    if (canned.hasOwnProperty('shared') && canned.shared === false && canned.createdBy !== req.user.id) {\n      winston.warn(\"Not allowed. User \" + req.user.id + \" can't delete a canned response of user \" + canned.createdBy);\n      return res.status(403).send({ success: false, error: \"Not allowed to delete a non administration canned response\"})\n    }\n  }\n  else {\n    winston.warn(\"User \" + req.user.id + \"trying to delete canned with role \" + user_role);\n    return res.status(401).send({ success: false, error: \"Unauthorized\"})\n  }\n\n  CannedResponse.remove({ _id: canned_id }, function (err, cannedResponse) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    // CannedResponseEvent.emit('CannedResponse.delete', CannedResponse);\n    res.json(cannedResponse);\n  });\n});\n\nrouter.get('/:cannedResponseid', function (req, res) {\n  winston.debug(req.body);\n  let user_id = req.user.id;\n  winston.verbose(\"CannedResponseRoute: user_id: \" + user_id);\n\n  CannedResponse.findById(req.params.cannedResponseid, function (err, cannedResponse) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!cannedResponse) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n\n    if (cannedResponse.createdBy !== user_id) {\n      return res.status(403).send({ success: false, msg: 'You are not allowed to get a canned response that is not yours.'})\n    }\n\n    res.json(cannedResponse);\n  });\n});\n\nrouter.get('/', function (req, res) {\n  var limit = 1000; // Number of CannedResponses per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('CannedResponse ROUTE - SKIP PAGE ', skip);\n\n  // var query = { \"id_project\": req.projectid, \"status\": {$lt:1000}};\n  var query = {\"id_project\": req.projectid, \"status\": { $lt:1000 }, $or:[ { shared: true }, { shared : { $exists: false }}, { createdBy: req.user._id } ] }\n\n  if (req.query.full_text) {\n    winston.debug('CannedResponse ROUTE req.query.fulltext', req.query.full_text);\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  return CannedResponse.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, cannedResponses) {\n      if (err) {\n        winston.error('CannedResponse ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n    \n        return res.json(cannedResponses);\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "pubmodules/canned/index.js",
    "content": "const cannedResponseRoute = require(\"./cannedResponseRoute\");\n\nmodule.exports = {cannedResponseRoute: cannedResponseRoute};"
  },
  {
    "path": "pubmodules/chatbotTemplates/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst templates = require(\"@tiledesk/tiledesk-chatbot-templates\");\nconst templatesRoute = templates.router;\n\n\nmodule.exports = { listener: listener, templatesRoute: templatesRoute }"
  },
  {
    "path": "pubmodules/chatbotTemplates/listener.js",
    "content": "const templates = require(\"@tiledesk/tiledesk-chatbot-templates\");\nconst winston = require(\"../../config/winston\");\nvar configGlobal = require('../../config/global');\n\nclass Listener {\n\n    listen(config) {\n     \n        winston.info(\"Chatbot Templates Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"chatbot templates config databaseUri: \" + config.databaseUri);\n        }\n\n        templates.startApp({\n            MONGODB_URL: config.databaseUri,\n            CHATBOT_TEMPLATES_LOG: 1\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Chatbot Templates proxy server successfully started.\");\n            } else {\n                winston.info(\"unable to start Tiledesk Chatbot Templates.\" + err);\n            }\n        })\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/dialogflow/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst dialogflow = require(\"@tiledesk/tiledesk-dialogflow-connector\");\nconst dialogflowRoute = dialogflow.router;\n\n\n\n\n\nmodule.exports = { listener: listener, dialogflowRoute: dialogflowRoute };"
  },
  {
    "path": "pubmodules/dialogflow/listener.js",
    "content": "const botEvent = require('../../event/botEvent');\nvar Faq_kb = require(\"../../models/faq_kb\");\nvar winston = require('../../config/winston');\nconst df = require(\"@tiledesk/tiledesk-dialogflow-connector\");\nvar configGlobal = require('../../config/global');\n\nvar port = process.env.PORT || '3000';\n\nconst BOT_DIALOGFLOW_ENDPOINT = process.env.BOT_DIALOGFLOW_ENDPOINT || \"http://localhost:\" + port+ \"/modules/dialogflow/tdbot/\";\nwinston.debug(\"BOT_DIALOGFLOW_ENDPOINT: \" + BOT_DIALOGFLOW_ENDPOINT);\n\n// if (BOT_DIALOGFLOW_ENDPOINT) {\n  winston.info(\"Dialogflow endpoint: \" + BOT_DIALOGFLOW_ENDPOINT);\n// } else {\n//    winston.info(\"Dialogflow endpoint not configured\");\n// }\n\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('Dialogflow apiUrl: '+ apiUrl);\n\nclass Listener {\n\n    listen(config) {\n\n        winston.debug('dialogflow Listener listen');   \n\n        var that = this;\n\n\n        df.startApp(\n            {\n              MONGODB_URI: config.databaseUri,   \n              API_ENDPOINT: apiUrl,\n              log: process.env.DIALOGFLOW_LOG\n            }, () => {\n              winston.info(\"Dialogflow route successfully started.\");                           \n            }\n          ); \n\n        botEvent.on('faqbot.create', function(bot) {\n            if (BOT_DIALOGFLOW_ENDPOINT) {\n\n                if (bot.type===\"dialogflow\") {\n                    // bot.url = BOT_DIALOGFLOW_ENDPOINT;\n\n                    Faq_kb.findByIdAndUpdate(bot.id, {\"url\":BOT_DIALOGFLOW_ENDPOINT}, { new: true, upsert: true }, function (err, savedFaq_kb) {\n\n                    // bot.save(function (err, savedFaq_kb) {\n                        if (err) {\n                         return winston.error('error saving faqkb dialogflow ', err)\n                        }\n                        botEvent.emit(\"faqbot.update\",savedFaq_kb); //cache invalidation\n                        winston.verbose('Saved faqkb dialogflow', savedFaq_kb.toObject())      \n                    });\n                }\n            }\n        });\n        \n    }\n\n}\n\nvar listener = new Listener();\n\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/emailNotification/index.js",
    "content": "const requestNotification = require(\"./requestNotification\");\nmodule.exports = {requestNotification:requestNotification};"
  },
  {
    "path": "pubmodules/emailNotification/requestNotification.js",
    "content": "'use strict';\n\nvar emailService = require(\"../../services/emailService\");\nvar Project = require(\"../../models/project\");\nvar Request = require(\"../../models/request\");\nvar RequestConstants = require(\"../../models/requestConstants\");\nvar Project_user = require(\"../../models/project_user\");\n\nvar User = require(\"../../models/user\");\nvar Lead = require(\"../../models/lead\");\nvar Message = require(\"../../models/message\");\nconst requestEvent = require('../../event/requestEvent');\nvar winston = require('../../config/winston');\nvar RoleConstants = require(\"../../models/roleConstants\");\nvar ChannelConstants = require(\"../../models/channelConstants\");\nvar cacheUtil = require('../../utils/cacheUtil');\nconst { TdCache } = require('../../utils/TdCache');\n\nconst messageEvent = require('../../event/messageEvent');\nvar mongoose = require('mongoose');\nvar jwt = require('jsonwebtoken');\nconst uuidv4 = require('uuid/v4');\nvar config = require('../../config/database');\nvar configGlobal = require('../../config/global');\n\nvar widgetConfig = require('../../config/widget');\nvar widgetTestLocation = process.env.WIDGET_LOCATION || widgetConfig.testLocation;\nwidgetTestLocation = widgetTestLocation + \"assets/twp/index.html\";\n\n\n\nlet configSecret = process.env.GLOBAL_SECRET || config.secret;\n\nvar pKey = process.env.GLOBAL_SECRET_OR_PRIVATE_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n  configSecret = pKey.replace(/\\\\n/g, '\\n');\n}\n\nlet apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.debug('********* RequestNotification apiUrl: ' + apiUrl);\n\n/** Redis cache for throttling pooled-request email. Injected by app (shared TdCache) or lazy-created fallback. */\nlet _pooledEmailTdCache = null;\nfunction getPooledEmailCache() {\n  if (_pooledEmailTdCache) return _pooledEmailTdCache;\n  const fallback = new TdCache({\n    host: process.env.CACHE_REDIS_HOST,\n    port: process.env.CACHE_REDIS_PORT,\n    password: process.env.CACHE_REDIS_PASSWORD\n  });\n  fallback.connect();\n  _pooledEmailTdCache = fallback;\n  return _pooledEmailTdCache;\n}\n\n/** TTL in seconds: do not send pooled email again for the same request within this window (avoids flooding when smart assignment retries) */\nconst POOLED_EMAIL_THROTTLE_TTL = Number(process.env.POOLED_EMAIL_THROTTLE_TTL) || 259200;\n\nclass RequestNotification {\n\n  constructor() {\n    this.enabled = true;\n    if (process.env.EMAIL_NOTIFICATION_ENABLED==\"false\" || process.env.EMAIL_NOTIFICATION_ENABLED==false) {\n        this.enabled = false;\n    }\n    winston.debug(\"RequestNotification this.enabled: \"+ this.enabled);\n  }\n\n  /**\n   * Reuse app's TdCache instance instead of opening a new Redis connection (optional, called by PubModulesManager).\n   * @param {TdCache} tdCache - shared Redis cache from app.get('redis_client')\n   */\n  setTdCache(tdCache) {\n    if (tdCache && typeof tdCache.setNX === 'function') {\n      _pooledEmailTdCache = tdCache;\n      winston.debug(\"RequestNotification using shared TdCache for pooled email throttle\");\n    }\n  }\n\nlisten() {\n\n  winston.debug(\"RequestNotification listen\");\n    var that = this; \n    \n\n      \n    if (this.enabled==true) {\n      winston.info(\"RequestNotification listener started\");\n    } else {\n        return winston.info(\"RequestNotification listener disabled\");\n    }\n\n\n    var messageCreateKey = 'message.create';\n    if (messageEvent.queueEnabled) {\n      messageCreateKey = 'message.create.queue';\n    }\n    winston.debug('RequestNotification messageCreateKey: ' + messageCreateKey);\n\n\n    messageEvent.on(messageCreateKey, function(message) {\n\n      setImmediate(() => {      \n        winston.debug(\"messageEvent.on(messageCreateKey\", message);\n        \n        if (message.attributes && message.attributes.subtype==='info') {\n          return winston.debug(\"not sending sendUserEmail for attributes.subtype info messages\");\n        }\n\n\n        if (message.request && (message.request.channel.name===ChannelConstants.EMAIL || message.request.channel.name===ChannelConstants.FORM)) {\n\n          //messages sent from admins or agents to requester\n          if (message.sender != message.request.lead.lead_id) {\n            winston.debug(\"sending sendToUserEmailChannelEmail for EMAIL or FORM channel\");\n             \n            //send email notification to requester (send also to followers)\n            return that.sendToUserEmailChannelEmail(message.id_project, message);        \n          } else { //messages sent from requester to agents or admins\n\n            if (message.text != message.request.first_text) {              \n              winston.debug(\"sending sendToAgentEmailChannelEmail for EMAIL or FORM channel\");\n\n              //send email notification to admins and agents(send also to followers)\n              return that.sendToAgentEmailChannelEmail(message.id_project, message);           \n            } else {\n              winston.debug(\"sending sendToAgentEmailChannelEmail for EMAIL or FORM channel disabled for first text message\")\n            }\n            \n          }\n          \n        } else {\n          winston.debug(\"sendUserEmail chat channel\");\n                       \n            //TODO     mandare email se ultimo messaggio > X MINUTI configurato in Notification . potresti usare request.updated_at ?\n\n            \n            \n            //messages sent from admins or agents\n            //send email notification to requester\n          if (message.request && message.request.lead && message.sender != message.request.lead.lead_id) {\n            winston.debug(\"sendUserEmail\");\n            winston.debug(\"sendUserEmail\", message);\n\n            // send an email only if offline and has an email (send also to followers)\n            return that.sendUserEmail(message.id_project, message);\n          } else { //send email  to followers\n            winston.debug(\"send direct email****\");\n\n            that.sendToFollower(message.id_project, message);\n\n          }\n          \n        }\n         \n      \n        \n      });\n     });\n\n     var requestCreateKey = 'request.create';\n     if (requestEvent.queueEnabled) {\n       requestCreateKey = 'request.create.queue';\n     }\n     winston.debug('RequestNotification requestCreateKey: ' + requestCreateKey);\n\n     requestEvent.on(requestCreateKey, function(request) {\n      winston.debug('requestEvent.on(requestCreateKey');\n      setImmediate(() => {\n   \n        /*\n        if (request && (request.channel.name===ChannelConstants.EMAIL || request.channel.name===ChannelConstants.FORM )) {\n          winston.debug(\"sending sendEmailChannelTakingNotification for EMAIL or FORM channel\");\n         that.sendEmailChannelTakingNotification(request.id_project, request)\n        } \n        */\n        \n        that.sendAgentEmail(request.id_project, request);\n        \n      });\n     });\n\n\n     var requestParticipantsUpdateKey = 'request.participants.update';\n     if (requestEvent.queueEnabled) {\n      requestParticipantsUpdateKey = 'request.participants.update.queue';\n     }\n     winston.debug('RequestNotification requestParticipantsUpdateKey: ' + requestParticipantsUpdateKey);\n\n     requestEvent.on(requestParticipantsUpdateKey, function(data) {\n\n      winston.debug(\"requestEvent request.participants.update\");\n\n      var request = data.request;\n      \n      setImmediate(() => {\n   \n         that.sendAgentEmail(request.id_project, request);\n      });\n     });\n\n\n\n    //  requestEvent.on(\"request.update.preflight\", function(request) {\n      \n    //   winston.info(\"requestEvent request.update.preflight\");\n\n    //   setImmediate(() => {\n   \n    //      that.sendAgentEmail(request.id_project, request);\n    //   });\n    //  });\n\n\n     \n\n    //  TODO Send email also for addAgent and reassign. Alessio request for pooled only?\n\n    var requestCloseExtendedKey = 'request.close.extended';\n    if (requestEvent.queueEnabled) {\n      requestCloseExtendedKey = 'request.close.extended.queue';\n    }\n    winston.debug('RequestNotification requestCloseExtendedKey: ' + requestCloseExtendedKey);    //request.close event here queued under job\n    requestEvent.on(requestCloseExtendedKey, function(data) {\n      winston.debug('requestEvent.on(requestCloseExtendedKey ' + requestCloseExtendedKey);\n      setImmediate(() => {\n        var request = data.request;\n        var notify = data.notify;\n        if (notify==false) {\n          winston.debug(\"sendTranscriptByEmail notify disabled\", request);\n          return;\n        }\n        var id_project = request.id_project;\n        var request_id  = request.request_id;\n \n        try {                \n          Project.findOne({_id: id_project, status: 100}, function(err, project){   \n            winston.debug(\"sendTranscriptByEmail\", project);\n\n            if (project && project.settings && project.settings.email && \n              project.settings.email.autoSendTranscriptToRequester &&  \n              project.settings.email.autoSendTranscriptToRequester === true \n              && \n              project.profile && \n              (\n                (project.profile.type === 'free' && project.trialExpired === false) || \n                (project.profile.type === 'payment' && project.isActiveSubscription === true)\n              )\n            ) \n              {\n\n              //send email to admin\n              Project_user.find({ id_project: id_project,  role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN]}, status: \"active\"} ).populate('id_user')\n              .exec(function (err, project_users) {\n\n                if (project_users && project_users.length>0) {\n                  project_users.forEach(project_user => {\n                    if (project_user.id_user && project_user.id_user.email) {\n                      return that.sendTranscriptByEmail(project_user.id_user.email, request_id, id_project, project).catch((err) => {\n                        winston.error(\"(RequestNotification) sendTranscriptByEmail error for admin\", err);\n                      });                              \n                    } else {\n                    }\n                  });  \n                }                      \n\n              });\n              //end send email to admin\n              winston.debug('Lead.findById ');\n              //send email to lead\n              return Lead.findById(request.requester_id, function(err, lead){\n                //if (lead && lead.email) {\n                  if (lead && lead.email) {\n                    return that.sendTranscriptByEmail(lead.email, request_id, id_project, project).catch((err) => {\n                      winston.error(\"(RequestNotification) sendTranscriptByEmail error for lead\", err);\n                    });\n                  }\n                  \n              });\n              //end send email to lead\n\n            } else {\n              winston.verbose(\"sendTranscriptByEmail disabled for project with id: \"+ id_project);\n            }\n          });\n        }catch(e) {\n          winston.error(\"error sendTranscriptByEmail \", e);\n        }\n\n      });\n  });\n}\n\n\nsendToUserEmailChannelEmail(projectid, message) {\n  winston.debug(\"sendToUserEmailChannelEmail\");\n  var that = this;\n  try {\n\n    if (!message.request) {\n      return winston.debug(\"This is a direct message\");\n    }\n\n\n    \n\n\n    \n\n    Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec(function(err, project){\n      if (err) {\n        return winston.error(err);\n      }\n  \n      if (!project) {\n       return winston.warn(\"Project not found\", projectid);\n      } \n      \n\n      // if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.blocked == true ) {\n      //   return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for  the conversations is blocked\");\n      // }\n  \n\n      // if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.enabled == false ) {\n      //   return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for the offline conversation is disabled\");\n      // }\n\n      let lead = message.request.lead;\n      winston.debug(\"sending channel email to lead \", lead);\n\n      \n      winston.debug(\"sending user email to  \"+ lead.email);\n\n      var signOptions = {\n        issuer:  'https://tiledesk.com',\n        subject:  'userexternal',\n        audience:  'https://tiledesk.com',\n        jwtid: uuidv4()\n      };\n\n      var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n      if (alg) {\n        signOptions.algorithm = alg;\n      }\n\n\n      var recipient = lead.lead_id;\n      winston.debug(\"recipient:\"+ recipient);\n\n      let userEmail = {_id: recipient, firstname: lead.fullname, lastname: lead.fullname, email: lead.email, attributes: lead.attributes};\n      winston.debug(\"userEmail  \",userEmail);\n\n\n      var token = jwt.sign(userEmail, configSecret, signOptions); //priv_jwt pp_jwt\n      winston.debug(\"token  \"+token);\n\n      var sourcePage = widgetTestLocation + \"?tiledesk_projectid=\" \n                  + projectid + \"&project_name=\"+encodeURIComponent(project.name)                   \n\n      if (message.request.sourcePage) {\n        sourcePage = message.request.sourcePage;                    \n      }\n      \n      if (sourcePage.indexOf(\"?\")===-1) {\n        sourcePage = sourcePage + \"?\";                   \n      }\n\n      sourcePage = sourcePage  \n                  + \"&tiledesk_recipientId=\"+message.request.request_id \n                  + \"&tiledesk_isOpen=true\";\n      \n                \n      sourcePage = apiUrl + \"/urls/redirect?path=\" + encodeURIComponent(sourcePage)\n      winston.debug(\"sourcePage  \"+sourcePage);\n\n\n      var tokenQueryString;      \n      if(sourcePage && sourcePage.indexOf('?')>-1) {  //controllo superfluo visto che lo metto prima? ma lascio comunque per indipendenza\n        tokenQueryString =  encodeURIComponent(\"&tiledesk_jwt=JWT \"+token)\n      }else {\n        tokenQueryString =  encodeURIComponent(\"?tiledesk_jwt=JWT \"+token);\n      }\n      winston.debug(\"tokenQueryString:  \"+tokenQueryString);\n      \n\n\n       // winston.info(\"savedRequest.followers\", savedRequest.followers);\n      // winston.info(\"savedRequest.followers.length:\"+ savedRequest.followers.length);\n      that.notifyFollowers(message.request, project, message);\n\n\n      if (message.attributes && message.attributes.subtype==='private') {\n        return winston.debug(\"not sending sendToUserEmailChannelEmail for attributes.subtype private messages\");\n      }           \n\n      // nn va bene qui\n      if (!message.request.lead || !message.request.lead.email) {\n        return winston.debug(\"The lead object is undefined or has empty email\");\n      }\n\n      emailService.sendEmailChannelNotification(message.request.lead.email, message, project, tokenQueryString, sourcePage);\n\n\n    });\n\n  } catch(e) {\n    winston.error(\"Error sending requestNotification email\", {error:e, projectid:projectid, message:message});\n  }\n}\n\n\nasync notifyFollowers(savedRequest, project, message) {\n\n  if (message.attributes && message.attributes.subtype==='info/support') {\n    return winston.debug(\"not sending notifyFollowers for attributes.subtype info/support messages\");\n  }     \n\n  if (message.attributes && message.attributes.subtype==='info') {\n    return winston.debug(\"not sending notifyFollowers for attributes.subtype info messages\");\n  }\n\n  if (!savedRequest) {\n    return winston.debug(\"not sending notifyFollowers for direct messages\");\n  }\n\n  // Cannot read property '_id' of undefined at RequestNotification.notifyFollowers (/usr/src/app/pubmodules/emailNotification/requestNotification.js:358:62) at /usr/src/app\n  // forse meglio .id\n  \n  var reqWithFollowers = await Request.findById(savedRequest._id).populate('followers').exec();  // cache_attention\n  winston.debug(\"reqWithFollowers\");\n  winston.debug(\"reqWithFollowers\",reqWithFollowers);\n  // console.log(\"reqWithFollowers\",reqWithFollowers);\n\n  if (reqWithFollowers.followers && reqWithFollowers.followers.length>0) {\n\n    winston.debug(\"reqWithFollowers.followers.length: \"+reqWithFollowers.followers.length);\n\n    reqWithFollowers.followers.forEach(project_user => {\n       winston.debug(\"project_user\", project_user); \n        //TODO skip participants from followers\n\n      var userid = project_user.id_user;\n      \n       if (project_user.settings && project_user.settings.email && project_user.settings.email.notification && project_user.settings.email.notification.conversation && project_user.settings.email.notification.conversation.ticket && project_user.settings.email.notification.conversation.ticket.follower == false ) {\n         return winston.verbose(\"RequestNotification email notification for the user with id \" +  userid+ \" the follower conversation ticket is disabled\");\n       }                  \n        \n         User.findOne({_id: userid , status: 100})\n          //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+userid)  //user_cache\n          .exec(function (err, user) {\n           if (err) {\n           //  winston.debug(err);\n           }\n           if (!user) {\n            winston.warn(\"User not found\", userid);\n           } else {\n             winston.info(\"Sending notifyFollowers to user with email: \"+ user.email);\n             if (user.emailverified) {\n              emailService.sendFollowerNotification(user.email, message, project);\n              // emailService.sendEmailChannelNotification(user.email, message, project);\n              \n              // emailService.sendNewAssignedAgentMessageEmailNotification(user.email, savedRequest, project, message);\n\n             }else {\n               winston.info(\"User email not verified\", user.email);\n             }\n           }\n         });\n\n         \n       });\n\n\n  }\n}\n\nsendToFollower(projectid, message) {\n  winston.debug(\"sendToFollower\");            \n  var that = this;\n    let savedRequest = message.request;\n      // send email\n      try {\n     \n     \n      Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec(async function(err, project){\n         if (err) {\n           return winston.error(err);\n         }\n     \n         if (!project) {\n          return winston.warn(\"Project not found\", projectid);\n         } else {\n           \n            winston.debug(\"project\", project);            \n  \n            // if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.blocked == true ) {\n            //   return winston.verbose(\"RequestNotification email notification for the project with id : \" + projectid + \" for all the conversations is blocked\");\n            // }\n  \n            winston.debug(\"savedRequest\", savedRequest);\n\n\n\n            // winston.info(\"savedRequest.followers\", savedRequest.followers);\n            // winston.info(\"savedRequest.followers.length:\"+ savedRequest.followers.length);\n            that.notifyFollowers(message.request, project, message);\n\n\n          }\n        })\n    } catch (e) {\n      winston.warn(\"Error sending requestNotification email\", {error:e, projectid:projectid, message: message, savedRequest:savedRequest}); //it's better to view error email at this stage\n    }\n}\n\nsendToAgentEmailChannelEmail(projectid, message) {\n  var that = this;\n    let savedRequest = message.request;\n      // send email\n      try {\n     \n     \n      Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec(async function(err, project){\n         if (err) {\n           return winston.error(err);\n         }\n     \n         if (!project) {\n          return winston.warn(\"Project not found\", projectid);\n         } else {\n           \n            winston.debug(\"project\", project);            \n  \n            if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.blocked == true ) {\n              return winston.verbose(\"RequestNotification email notification for the project with id : \" + projectid + \" for all the conversations is blocked\");\n            }\n  \n            winston.debug(\"savedRequest\", savedRequest);\n\n\n\n            // winston.info(\"savedRequest.followers\", savedRequest.followers);\n            // winston.info(\"savedRequest.followers.length:\"+ savedRequest.followers.length);\n            that.notifyFollowers(message.request, project, message);\n\n\n  \n    //         UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'status' of undefined\n    // at /Users/andrealeo/dev/chat21/ti\n\n                // TODO fare il controllo anche sul dipartimento con modalità assigned o pooled\n                   if (savedRequest.status==RequestConstants.UNASSIGNED) { //POOLED\n  \n                    if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.ticket && project.settings.email.notification.conversation.ticket.pooled == false ) {\n                      return winston.info(\"RequestNotification email notification for the project with id : \" + projectid + \" for the pooled conversation ticket is disabled\");\n                    }\n                    \n                    if (!savedRequest.snapshot) {\n                      return winston.warn(\"RequestNotification savedRequest.snapshot is null :(. You are closing an old request?\");\n                    }\n\n\n\n                    \n                    var snapshotAgents = await Request.findById(savedRequest.id).select({\"snapshot\":1}).exec();\n\n                    winston.debug('snapshotAgents',snapshotAgents);                              \n\n\n                    // winston.info(\"savedRequest.snapshot.agents\", savedRequest.snapshot.agents);\n                    // agents è selected false quindi nn va sicuro\n                    if (!snapshotAgents.snapshot.agents) {\n                      return winston.warn(\"RequestNotification snapshotAgents.snapshot.agents is null :(. You are closing an old request?\", savedRequest);\n                    }\n                    \n                    //  var allAgents = savedRequest.agents;\n                     var allAgents = snapshotAgents.snapshot.agents;\n                    // winston.debug(\"allAgents\", allAgents);\n     \n                     allAgents.forEach(project_user => {\n                    //  winston.debug(\"project_user\", project_user); //DON'T UNCOMMENT THIS. OTHERWISE this.agents.filter of models/request.js:availableAgentsCount has .filter not found.\n     \n  \n                    var userid = project_user.id_user;\n                    \n                     if (project_user.settings && project_user.settings.email && project_user.settings.email.notification && project_user.settings.email.notification.conversation && project_user.settings.email.notification.conversation.ticket && project_user.settings.email.notification.conversation.ticket.pooled == false ) {\n                       return winston.verbose(\"RequestNotification email notification for the user with id \" +  userid+ \" the pooled conversation ticket is disabled\");\n                     }                  \n                      \n                       User.findOne({_id: userid , status: 100})\n                        //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+userid)   //user_cache\n                        .exec(function (err, user) {\n                         if (err) {\n                         //  winston.debug(err);\n                         }\n                         if (!user) {\n                          winston.warn(\"User not found\", userid);\n                         } else {\n                           winston.debug(\"Sending sendNewPooledMessageNotification to user with email: \"+ user.email);\n                           if (user.emailverified) {\n                             emailService.sendNewPooledMessageEmailNotification(user.email, savedRequest, project, message);\n                           }else {\n                             winston.verbose(\"User email not verified\", user.email);\n                           }\n                         }\n                       });\n     \n                       \n                     });\n     \n                     }\n  \n                     // TODO fare il controllo anche sul dipartimento con modalità assigned o pooled\n                     else if (savedRequest.status==RequestConstants.ASSIGNED) { //ASSIGNED\n  \n                      if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.ticket && project.settings.email.notification.conversation.ticket.assigned == false ) {\n                        return winston.verbose(\"RequestNotification email notification for the project with id : \" + projectid + \" for the assigned conversation ticket is disabled\");\n                      }\n  \n  \n                      var assignedId = savedRequest.participants[0];\n  \n                      //  winston.info(\"assignedId1:\"+ assignedId);\n  \n                      //  if (!assignedId) {\n                      //    console.log(\"attention90\", savedRequest);\n                      //  }\n  \n  \n                      \n                      Project_user.findOne( { id_user:assignedId, id_project: projectid, status: \"active\"}) //attento in 2.1.14.2\n                      .exec(function (err, project_user) {\n                        \n                          winston.debug(\"project_user notification\", project_user);\n                          if (project_user && project_user.settings && project_user.settings.email && project_user.settings.email.notification && project_user.settings.email.notification.conversation && project_user.settings.email.notification.conversation.ticket && project_user.settings.email.notification.conversation.ticket.assigned &&  project_user.settings.email.notification.conversation.ticket.assigned.toyou == false ) {\n                            return winston.info(\"RequestNotification email notification for the user with id : \" + assignedId + \" for the pooled conversation ticket is disabled\");\n                          }\n  \n                          // botprefix\n                          if (assignedId.startsWith(\"bot_\")) {\n                            return ;\n                          }\n        \n                          User.findOne({_id: assignedId, status: 100})\n                            //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+assignedId)    //user_cache\n                            .exec(function (err, user) {\n                            if (err) {\n                              winston.error(\"Error sending requestNotification email to \" + savedRequest.participants[0], err);\n                            }\n                            if (!user) {\n                              winston.warn(\"User not found\",  savedRequest.participants[0]);\n                            } else {\n                              winston.verbose(\"Sending sendNewAssignedAgentMessageEmailNotification to user with email: \"+ user.email);\n                              //  if (user.emailverified) {    enable it?     send anyway to improve engagment for new account     \n                              // attento cambia           \n                                emailService.sendNewAssignedAgentMessageEmailNotification(user.email, savedRequest, project, message);\n                              //  }\n                            }\n                          });\n  \n                        });\n  \n                     }\n  \n  \n  \n                     else {\n                      return winston.debug(\"Other states\");\n                     }\n     \n     \n           \n           }\n     \n     });\n     \n     } catch (e) {\n       winston.warn(\"Error sending requestNotification email\", {error:e, projectid:projectid, message: message, savedRequest:savedRequest}); //it's better to view error email at this stage\n     }\n     //end send email\n     \n     }\n  \n\n\n\n//unused\nsendEmailChannelTakingNotification(projectid, request) {\n  try {\n\n\n    if (!request.lead || !request.lead.email) {\n      return winston.debug(\"The lead object is undefined or has empty email\");\n    }\n\n    Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec(function(err, project){\n      if (err) {\n        return winston.error(err);\n      }\n  \n      if (!project) {\n       return winston.warn(\"Project not found\", projectid);\n      } \n\n      // if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.blocked == true ) {\n      //   return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for  the conversations is blocked\");\n      // }\n  \n\n      // if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.enabled == false ) {\n      //   return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for the offline conversation is disabled\");\n      // }\n    emailService.sendEmailChannelTakingNotification(request.lead.email, request, project);\n  \n\n    });\n\n  } catch(e) {\n    winston.error(\"Error sending requestNotification email\", {error:e, projectid:projectid, message:message});\n  }\n}\n\n\n\n\n\nsendUserEmail(projectid, message) {\n  winston.debug(\"sendUserEmail\");\n  var that = this;\n\n \n  try {\n\n    if (!message.request) {\n      return winston.debug(\"This is a direct message\");\n    }\n   \n\n\n    \n\n    Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec(function(err, project){\n      if (err) {\n        return winston.error(err);\n      }\n  \n      if (!project) {\n       return winston.warn(\"Project not found\", projectid);\n      } \n\n      winston.debug(\"notifyFollowers\");\n      that.notifyFollowers(message.request, project, message);\n\n\n\n      if (process.env.DISABLE_SEND_OFFLINE_EMAIL === \"true\" || process.env.DISABLE_SEND_OFFLINE_EMAIL === true ) {\n        return winston.debug(\"DISABLE_SEND_OFFLINE_EMAIL disabled\");\n      }\n      if (message.attributes && message.attributes.subtype==='info/support') {\n        return winston.debug(\"not sending sendUserEmail for attributes.subtype info/support messages\");\n      }     \n    \n      if (message.attributes && message.attributes.subtype==='info') {\n        return winston.debug(\"not sending sendUserEmail for attributes.subtype info messages\");\n      }\n\n\n      \n      if (!message.request.lead || !message.request.lead.email) {\n        return winston.debug(\"The lead object is undefined or has empty email\");\n      }\n\n      if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.blocked == true ) {\n        return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for  the conversations is blocked\");\n      }\n  \n\n      if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.offline && project.settings.email.notification.conversation.offline.enabled == false ) {\n        return winston.info(\"RequestNotification offline email notification for the project with id : \" + projectid + \" for the offline conversation is disabled\");\n      }\n      \n      \n\n        var recipient = message.request.lead.lead_id;\n        winston.debug(\"recipient:\"+ recipient);\n\n        var isObjectId = mongoose.Types.ObjectId.isValid(recipient);\n        winston.debug(\"isObjectId:\"+ isObjectId);\n\n        var queryProjectUser ={ id_project: projectid, status: \"active\"};\n\n        \n        if (isObjectId) {          \n          queryProjectUser.id_user = recipient\n        }else {\n          queryProjectUser.uuid_user = recipient\n        }\n        winston.debug(\"queryProjectUser\", queryProjectUser);\n\n        \n        Project_user.findOne( queryProjectUser)\n        .exec(function (err, project_user) {\n\n          winston.debug(\"project_user\", project_user);\n\n          if (!project_user) {\n            return winston.warn(\"Project_user not found with query \", queryProjectUser);\n          }\n         \n          if (!project_user.presence || (project_user.presence && project_user.presence.status === \"offline\")) {\n\n             //send email to lead\n            return Lead.findOne({lead_id: recipient}, function(err, lead){\n              winston.debug(\"lead\", lead);     //TODO  lead  is already present in request.lead\n              if (lead && lead.email) {\n                  winston.debug(\"sending user email to  \"+ lead.email);\n\n                  var signOptions = {\n                    issuer:  'https://tiledesk.com',\n                    subject:  'guest',\n                    audience:  'https://tiledesk.com',\n                    jwtid: uuidv4()\n                  };\n\n                  var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n                  if (alg) {\n                    signOptions.algorithm = alg;\n                  }\n\n                  let userAnonym = {_id: recipient, firstname: lead.fullname, lastname: lead.fullname, email: lead.email, attributes: lead.attributes};\n                  winston.debug(\"userAnonym  \",userAnonym);\n\n        \n                  var token = jwt.sign(userAnonym, configSecret, signOptions); //priv_jwt pp_jwt\n                  winston.debug(\"token  \"+token);\n\n                  var sourcePage = widgetTestLocation + \"?tiledesk_projectid=\" \n                  + projectid + \"&project_name=\"+encodeURIComponent(project.name)                   \n\n                  if (message.request.sourcePage) {\n                    sourcePage = message.request.sourcePage;                    \n                  }\n                  \n                  if (sourcePage && sourcePage.indexOf(\"?\")===-1) {\n                    sourcePage = sourcePage + \"?\";                   \n                  }\n\n                  sourcePage = sourcePage  \n                              + \"&tiledesk_recipientId=\"+message.request.request_id \n                              + \"&tiledesk_isOpen=true\";\n\n                  sourcePage = apiUrl + \"/urls/redirect?path=\" + encodeURIComponent(sourcePage)\n\n                  winston.debug(\"sourcePage  \"+sourcePage);\n\n                  var tokenQueryString;\n                  if(sourcePage && sourcePage.indexOf('?')>-1) {  //controllo superfluo visto che lo metto prima? ma lascio comunque per indipendenza\n                    tokenQueryString =  encodeURIComponent(\"&tiledesk_jwt=JWT \"+token)\n                  }else {\n                    tokenQueryString =  encodeURIComponent(\"?tiledesk_jwt=JWT \"+token);\n                  }\n                  winston.debug(\"tokenQueryString:  \"+tokenQueryString);\n                  \n                  //send email unverified so spam check?\n                  emailService.sendNewMessageNotification(lead.email, message, project, tokenQueryString, sourcePage);\n              } \n                \n            });\n\n          }\n\n        });       \n\n\n    });\n\n  } catch(e) {\n    winston.error(\"Error sending requestNotification email\", {error:e, projectid:projectid, message:message});\n  }\n}\n\nsendAgentEmail(projectid, savedRequest) {\n    // send email\n    try {\n   \n  //  console.log(\"sendAgentEmail\")\n    if (savedRequest.preflight === true) {   //only for channel email and form preflight is false otherwise request.participants.update is used i think?\n      winston.debug(\"preflight request sendAgentEmail disabled\")\n      return 0;\n    }\n\n    Project.findOne({_id: projectid, status: 100}).select(\"+settings\").exec( async function(err, project){\n       if (err) {\n         return winston.error(err);\n       }\n   \n       if (!project) {\n        return winston.warn(\"Project not found\", projectid);\n       } else {\n         \n          winston.debug(\"project\", project);            \n\n          if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.blocked == true ) {\n            return winston.verbose(\"RequestNotification email notification for the project with id : \" + projectid + \" for all the conversations is blocked\");\n          }\n\n          winston.debug(\"savedRequest: \" + JSON.stringify(savedRequest));\n\n              // TODO fare il controllo anche sul dipartimento con modalità assigned o pooled\n                 if (savedRequest.status==RequestConstants.UNASSIGNED) { //POOLED\n\n                  winston.debug(\"savedRequest.status==RequestConstants.UNASSIGNED\");        \n\n                  if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.pooled == false ) {\n                    return winston.info(\"RequestNotification email notification for the project with id : \" + projectid + \" for the pooled conversation is disabled\");\n                  }\n                  if (!savedRequest.snapshot) {\n                    return winston.warn(\"RequestNotification savedRequest.snapshot is null :(. You are closing an old request?\");\n                  }\n\n                  // Throttle: send pooled email only once per request within TTL (avoids flooding when smart assignment keeps retrying)\n                  const requestId = (savedRequest._id || savedRequest.id).toString();\n                  const pooledEmailKey = 'pooled_request_email:' + requestId;\n                  try {\n                    const shouldSend = await getPooledEmailCache().setNX(pooledEmailKey, '1', POOLED_EMAIL_THROTTLE_TTL);\n                    if (!shouldSend) {\n                      return winston.debug(\"RequestNotification pooled email already sent for request \" + requestId + \" (Redis throttle, TTL \" + POOLED_EMAIL_THROTTLE_TTL + \"s)\");\n                    }\n                  } catch (redisErr) {\n                    winston.warn(\"RequestNotification Redis setNX for pooled email throttle failed, skipping send\", { error: redisErr, requestId });\n                    return;\n                  }\n\n                 \n                  var snapshotAgents = savedRequest; //riassegno varibile cosi nn cambio righe successive\n\n                  \n\n\n                  // winston.info(\"savedRequest.snapshot.agents\", savedRequest.snapshot.agents);\n                  // agents è selected false quindi nn va sicuro\n                  if (!snapshotAgents.snapshot.agents) {\n                    //return winston.warn(\"RequestNotification snapshotAgents.snapshot.agents is null :(. You are closing an old request?\", savedRequest);\n\n                  // agents già c'è in quanto viene creato con departmentService.getOperator nella request.create ma nn c'è per request.participants.update\n                      snapshotAgents = await Request.findById(savedRequest.id).select({\"snapshot\":1}).exec();\n                      winston.debug('load snapshotAgents with Request.findById ');                              \n                  }\n                  winston.debug('snapshotAgents', snapshotAgents);                              \n\n                  if (!snapshotAgents.snapshot.agents) {\n                    return winston.warn(\"RequestNotification snapshotAgents.snapshot.agents is null :(. You are closing an old request?\", savedRequest);\n                  }\n\n                  //  var allAgents = savedRequest.agents;\n                   var allAgents = snapshotAgents.snapshot.agents;\n            \n                  // //  var allAgents = savedRequest.agents;\n                  //  var allAgents = savedRequest.snapshot.agents;\n                  // // winston.debug(\"allAgents\", allAgents);\n   \n                  allAgents.forEach(project_user => {\n                  //  winston.debug(\"project_user\", project_user); //DON'T UNCOMMENT THIS. OTHERWISE this.agents.filter of models/request.js:availableAgentsCount has .filter not found.\n   \n\n                  var userid = project_user.id_user;\n                  \n                   if (project_user.settings && project_user.settings.email && project_user.settings.email.notification && project_user.settings.email.notification.conversation && project_user.settings.email.notification.conversation.pooled == false ) {\n                     return winston.verbose(\"RequestNotification email notification for the user with id \" +  userid+ \" the pooled conversation is disabled\");\n                   }                  \n                    \n                     User.findOne({_id: userid , status: 100})\n                      //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+userid)    //user_cache\n                      .exec(function (err, user) {\n                       if (err) {\n                       //  winston.debug(err);\n                       }\n                       if (!user) {\n                        winston.warn(\"User not found\", userid);\n                       } else {\n                         winston.verbose(\"Sending sendNewPooledRequestNotification to user with email: \"+ user.email);\n                         if (user.emailverified) {\n                           emailService.sendNewPooledRequestNotification(user.email, savedRequest, project);\n                         }else {\n                           winston.verbose(\"User email not verified\", user.email);\n                         }\n                       }\n                     });\n   \n                     \n                   });\n   \n                   }\n\n                   // TODO fare il controllo anche sul dipartimento con modalità assigned o pooled\n                   else if (savedRequest.status==RequestConstants.ASSIGNED) { //ASSIGNED\n\n                    winston.debug(\"savedRequest.status==RequestConstants.ASSIGNED\");        \n\n                    if (project.settings && project.settings.email && project.settings.email.notification && project.settings.email.notification.conversation && project.settings.email.notification.conversation.assigned == false ) {\n                      return winston.verbose(\"RequestNotification email notification for the project with id : \" + projectid + \" for the assigned conversation is disabled\");\n                    }\n\n\n                    var assignedId = savedRequest.participants[0];\n\n                    //  winston.info(\"assignedId1:\"+ assignedId);\n\n                    //  if (!assignedId) {\n                    //    console.log(\"attention90\", savedRequest);\n                    //  }\n\n\n                    \n                    Project_user.findOne( { id_user:assignedId, id_project: projectid, status: \"active\"}) \n                    .exec(function (err, project_user) {\n                      \n                      // botprefix\n                      if (assignedId.startsWith(\"bot_\")) {\n                        return ;\n                      }\n                      \n                       if (err) {\n                        return winston.error(\"RequestNotification email notification error getting project_user\", err);\n                       }\n                        winston.debug(\"project_user notification\", project_user);\n                        if (project_user && project_user.settings && project_user.settings.email && project_user.settings.email.notification && project_user.settings.email.notification.conversation && project_user.settings.email.notification.conversation.assigned &&  project_user.settings.email.notification.conversation.assigned.toyou == false ) {\n                          return winston.info(\"RequestNotification email notification for the user with id : \" + assignedId + \" for the pooled conversation is disabled\");\n                        }\n\n                        \n      \n                        if (!project_user) {\n                          return winston.warn(\"RequestNotification email notification for the user with id : \" + assignedId + \" not found project_user\");\n                        }\n                        User.findOne({_id: assignedId, status: 100})\n                          //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+assignedId)    //user_cache\n                          .exec(function (err, user) {\n                          if (err) {\n                            winston.error(\"Error sending requestNotification email to \" + savedRequest.participants[0], err);\n                          }\n                          if (!user) {\n                            winston.warn(\"User not found\",  savedRequest.participants[0]);\n                          } else {\n                            winston.debug(\"Sending sendNewAssignedRequestNotification to user with email\", user.email);\n                            //  if (user.emailverified) {    enable it?     send anyway to improve engagment for new account    \n                            \n                            \n                            // var signOptions = {\n                            //   issuer:  'https://tiledesk.com',\n                            //   subject:  'user',\n                            //   audience:  'https://tiledesk.com',\n                            //   jwtid: uuidv4()        \n                            // };\n          \n                            // let userObject = {_id: user._id, firstname: user.firstname, lastname: user.lastname, email: user.email, attributes: user.attributes};\n                            // winston.debug(\"userObject  \",userObject);\n          \n                  \n                            // var agentToken = jwt.sign(userObject, configSecret, signOptions);\n                            // winston.debug(\"agentToken  \"+agentToken);\n\n                            \n\n\n                            emailService.sendNewAssignedRequestNotification(user.email, savedRequest, project);\n                            //  }\n                          }\n                        });\n\n                      });\n\n                   }\n\n\n\n                   else {\n                    return winston.debug(\"Other states\");\n                   }\n   \n   \n         \n         }\n   \n   });\n   \n   } catch (e) {\n     winston.warn(\"Error sending requestNotification email\", {error:e, projectid:projectid, savedRequest:savedRequest}); //it's better to view error email at this stage\n   }\n   //end send email\n   \n   }\n\n\n\n\n\n\n   sendTranscriptByEmail(sendTo, request_id, id_project, project) {\n    return new Promise(function (resolve, reject) {\n      return Request.findOne({request_id: request_id, id_project: id_project})\n      .populate('department')\n      // .cache(cacheUtil.defaultTTL, \"/\"+id_project+\"/requests/request_id/populate/department/\"+request_id)\n      .exec(function(err, request) { \n      if (err){\n        winston.error(err);\n        return reject(err);\n      }\n      if (!request) {\n        winston.error(\"Request not found for request_id \"+ request_id + \" and id_project \" + id_project);\n        return reject(\"Request not found for request_id \"+ request_id  + \" and id_project \" + id_project);\n      }\n      \n\n\n      return Message.find({\"recipient\": request_id, id_project : id_project})\n        .sort({createdAt: 'asc'})\n        .exec(function(err, messages) { \n        if (err) {\n          return res.status(500).send({success: false, msg: 'Error getting messages.'});\n        }\n\n        if(!messages){\n          return reject(err);\n        }\n\n      \n\n        emailService.sendRequestTranscript(sendTo, messages, request, project);\n        winston.verbose(\"sendTranscriptByEmail sent\");\n        return resolve({sendTo: sendTo, messages: messages, request: request});\n\n      \n      });\n\n      });\n    });\n  }\n   \n   \n}\n \n \n \n \nvar requestNotification = new RequestNotification();\n\n\nmodule.exports = requestNotification;\n "
  },
  {
    "path": "pubmodules/entityEraser/eraserInterceptor.js",
    "content": "\nconst authEvent = require('../../event/authEvent');\nconst projectEvent = require('../../event/projectEvent');\nvar winston = require('../../config/winston');\nvar Project = require(\"../../models/project\");\nvar Project_user = require(\"../../models/project_user\");\nvar RoleConstants = require(\"../../models/roleConstants\");\n\nclass EntityInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"EntityInterceptor listener start \");\n        \n\n        authEvent.on('user.delete',  function(data) {          \n\n            var userid = data.user.id;\n            var query = { id_user: userid,  role: { $in : [RoleConstants.OWNER]} } ;\n\n            winston.debug(\"query: \", query);\n\n                setImmediate(() => {\n                    Project_user.find(query).populate('id_user')\n                        .exec(function (err, project_users) {\n                         if (project_users && project_users.length>0) {\n                             project_users.forEach(pu => {                                 \n                                 Project.findByIdAndUpdate({_id: pu.id_project}, {status:0}, { new: true, upsert: true }, function (err, project) {\n                                    if (err) {\n                                      winston.error('Error deleting project ', err);\n                                    }\n                                    winston.info('Deleted project with id: '+project.id);\n                                    projectEvent.emit('project.delete', project );\n                                  });\n                             });\n                         }\n                        });\n                      \n                    \n                });\n        });\n        \n\n       \n\n    }\n\n\n    \n}\n\nvar entityInterceptor = new EntityInterceptor();\nmodule.exports = entityInterceptor;"
  },
  {
    "path": "pubmodules/entityEraser/index.js",
    "content": "const eraserInterceptor = require(\"./eraserInterceptor\");\nmodule.exports = {eraserInterceptor:eraserInterceptor};"
  },
  {
    "path": "pubmodules/events/analyticEventsResult.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n// var winston = require('../../config/winston');\n\nvar AnalyticEventsResultSchema = new Schema({\n  count: {\n    type: Number,\n     required: true\n  }}, { collection: 'events' }\n);\n\nvar analyticEventsResult= mongoose.model('analyticEventsResultSchema', AnalyticEventsResultSchema);\n\n\n\nmodule.exports = analyticEventsResult;\n"
  },
  {
    "path": "pubmodules/events/event.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\nvar winston = require('../../config/winston');\n\n\nvar EventSchema = new Schema({\n  \n  name: { \n    type: String,\n    required: true,\n    index: true\n  },\n  attributes: {\n    type: Object,\n  },\n  // TODO FALLO EMBED per performance\n  project_user: {\n    type: Schema.Types.ObjectId,\n    ref: 'project_user',\n    index: true,\n    required: false\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index: true\n  },\n  status: {\n    type: String,\n    required: true,\n    default: \"stardard\",\n    index: true\n  },\n  // id_user: {\n  //   type: String,\n  //   required: true,\n  //   index: true\n  // },\n  createdBy: {\n    type: String,\n    required: true,\n    index: true\n  },\n},{\n  timestamps: true\n}\n);\n\nEventSchema.index({ id_project: 1, name: 1, createdAt: -1 });\n\nvar event = mongoose.model('event', EventSchema);\n\n\nmodule.exports = event;\n"
  },
  {
    "path": "pubmodules/events/event2Event.js",
    "content": "var winston = require('../../config/winston');\n\n\nvar EventEmitter2 = require('eventemitter2').EventEmitter2;\n\n\nclass Event2Event extends EventEmitter2 {}\n\nconst event2Event = new Event2Event({\n\n  //\n  // set this to `true` to use wildcards. It defaults to `false`.\n  //\n  wildcard: true,\n});\n\nwinston.debug(\"event2Event init\");\n\n\n\nmodule.exports = event2Event;\n"
  },
  {
    "path": "pubmodules/events/eventEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass EventEvent extends EventEmitter {}\n\nconst eventEvent = new EventEvent();\n\n\n\n\nmodule.exports = eventEvent;\n"
  },
  {
    "path": "pubmodules/events/eventRoute.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Event = require(\"./event\");\nvar winston = require('../../config/winston');\nvar validtoken = require('../../middleware/valid-token');\nconst eventService = require('./eventService');\nconst { check, validationResult } = require('express-validator');\nvar passport = require('passport');\nrequire('../../middleware/passport')(passport);\nvar roleChecker = require('../../middleware/has-role');\n\nconst messageEvent = require('../../event/messageEvent');\n\n\nrouter.post('/', [\n  passport.authenticate(['basic', 'jwt'], \n  { session: false }), \n  validtoken, \n  // roleChecker.hasRole('guest'),\n\n  // >Cannot read property 'id' of undefined</h1>\n\n  roleChecker.hasRoleOrTypes('guest', ['bot','subscription']),\n  check('name').notEmpty(),  \n],function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n    \n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n    var pu = undefined;\n    if (req.projectuser) { //if the bot creates the event -> pu is undefined\n      pu = req.projectuser.id\n    }   \n    // // emit(name, attributes, id_project, project_user, createdBy, status, user) {\n    eventService.emit(req.body.name, req.body.attributes, req.projectid, pu, req.user.id, undefined, req.user).then(function(event) {\n\n      res.json(event);\n    }).catch(function(err) {\n      winston.error('Error saving the event', err);\n      return res.status(500).send({success: false, msg: 'Error saving the event', error: err.message });\n    });\n\n});\n\n\n\nrouter.get('/:eventid', \n  [passport.authenticate(['basic', 'jwt'], \n  { session: false }), \n  validtoken, \n  roleChecker.hasRole('agent')],\n  function (req, res) {\n\n  winston.debug(req.body);\n\n  Event.findById(req.params.eventid, function (err, event) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!event) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(event);\n  });\n});\n\n\n\nrouter.get('/',   [passport.authenticate(['basic', 'jwt'], \n  { session: false }), \n  validtoken, \n  roleChecker.hasRole('agent')],\n  function (req, res) {\n  var limit = 40; // Number of leads per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('Event ROUTE - SKIP PAGE ', skip);\n\n\n  var query = { \"id_project\": req.projectid};\n\n  if (req.query.project_user) {\n    var project_user = req.query.project_user;\n    query.project_user = project_user;\n  }\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  //console.log(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  //console.log(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  return Event.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, events) {\n      if (err) {\n        winston.error('Event ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n      //  collection.count is deprecated, and will be removed in a future version. Use Collection.countDocuments or Collection.estimatedDocumentCount instead\n      return Event.countDocuments(query, function (err, totalRowCount) {\n\n        var objectToReturn = {\n          perPage: limit,\n          count: totalRowCount,\n          events: events\n        };\n\n        return res.json(objectToReturn);\n      });\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "pubmodules/events/eventService.js",
    "content": "'use strict';\n\nvar Event = require(\"./event\");\nconst eventEvent = require('./eventEvent');\nconst event2Event = require('./event2Event');\nvar winston = require('../../config/winston');\n\n\nclass EventService {\n\n\n  // emit2(name, attributes, id_project, project_user, id_user, createdBy) {\n\n  //   return new Promise(function (resolve, reject) {\n\n  \n  //     var newEvent = new Event({\n  //       name: name,\n  //       attributes: attributes,\n  //       id_project: id_project,\n  //       project_user: project_user,\n  //       id_user: id_user,\n  //       createdBy: createdBy,\n  //       updatedBy: createdBy\n  //     });\n    \n  //     newEvent.save(function(err, savedEvent) {\n  //       if (err) {\n  //         winston.error('Error saving the event '+ JSON.stringify(savedEvent), err)\n  //         return reject(err);\n  //       }\n  //       savedEvent.populate('project_user',function (err, savedEventPopulated){\n  //         eventEvent.emit('event.emit', savedEventPopulated);\n  //         event2Event.emit(name, savedEventPopulated);\n  //       });\n       \n  //       return resolve(savedEvent);\n  //     });\n  //   });\n\n  // }\n\n  emit(name, attributes, id_project, project_user, createdBy, status, user) {\n\n    return new Promise(function (resolve, reject) {\n  \n      var newEvent = new Event({\n        name: name,\n        attributes: attributes,\n        id_project: id_project,\n        project_user: project_user,\n        createdBy: createdBy,\n        updatedBy: createdBy,\n        status: status\n      });\n    \n      //TODO do not save volatile events\n      \n      newEvent.save(function(err, savedEvent) {\n        if (err) {\n          winston.error('Error saving the event '+ JSON.stringify(savedEvent), err)\n          return reject(err);\n        }\n        savedEvent.populate({path:'project_user', \n          populate:{path:'id_user'}                \n        },function (err, savedEventPopulated) {\n          \n          var savedEventPopulatedJson = savedEventPopulated.toJSON();\n\n          if (user) {\n            savedEventPopulatedJson.user = user;\n          }else {\n            winston.debug(\"Attention eventService emit user is empty\");\n          }\n          \n          savedEventPopulatedJson.id = savedEventPopulatedJson._id;\n          winston.debug(\"savedEventPopulatedJson\", savedEventPopulatedJson);\n          eventEvent.emit('event.emit', savedEventPopulatedJson);\n          eventEvent.emit('event.emit.'+name, savedEventPopulatedJson);\n          \n          event2Event.emit(name, savedEventPopulatedJson);\n        });\n\n       \n        return resolve(savedEvent);\n      });\n    });\n\n  }\n\n}\nvar eventService = new EventService();\n\n\n\n\nmodule.exports = eventService;\n"
  },
  {
    "path": "pubmodules/events/index.js",
    "content": ""
  },
  {
    "path": "pubmodules/events/test/eventRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../../../models/user');\nvar Project_user = require('../../../models/project_user');\n\nvar projectService = require('../../../services/projectService');\nvar userService = require('../../../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../../../app');\nlet should = chai.should();\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('EventRoute', () => {\n\n  describe('/create', () => {\n \n   \n\n    it('create', (done) => {\n\n        \n       var email = \"test-signup-eventroute\" + Date.now() + \"@email.com\";\n       var pwd = \"pwd\";\n\n        userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n            projectService.create(\"test-EventRoute-create\", savedUser._id).then(function(savedProject) {                                              \n                    chai.request(server)\n                        .post('/'+ savedProject._id + '/events')\n                        .set('content-type', 'application/json')\n                        .auth(email, pwd)\n                        .send({\"name\":\"event1\", attributes: {\"attr1\":\"val1\"}})\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\",  res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"event1\");                                                                              \n                            expect(res.body.attributes.attr1).to.equal(\"val1\");                                                                              \n                        \n                            Project_user.findOne({ id_user:  savedUser.id, status: \"active\"} )\n                            .populate('events')\n                            .exec(function (err, project_user) {\n                                if (log) { console.log(\"project_user\",  project_user.toJSON()); }\n                                expect(project_user.events.length).to.equal(1);  \n                                done();\n                            });\n                           \n                        });\n\n                        \n                });\n                });\n                \n    }).timeout(20000);\n\n\n\n\n\n\n\n\n});\n\n});\n\n\n"
  },
  {
    "path": "pubmodules/kaleyra/index.js",
    "content": "const listener = require('./listener');\n\nconst kaleyra = require('@tiledesk/tiledesk-kaleyra-proxy');\nconst kaleyraRoute = kaleyra.router;\n\nmodule.exports = { listener: listener, kaleyraRoute: kaleyraRoute }"
  },
  {
    "path": "pubmodules/kaleyra/listener.js",
    "content": "const kaleyra = require(\"@tiledesk/tiledesk-kaleyra-proxy\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info(\"Kaleyra apiUrl: \" + apiUrl);\n \nclass Listener {\n\n    listen(config) {\n        winston.info(\"Kaleyra Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"kaleyra config databaseUri: \" + config.databaseUri);\n        }\n\n        if (!process.env.KALEYRA_API_URL || !process.env.API_KEY) {\n            winston.info(\"Skip Kaleyra startApp\")\n        } else {\n            kaleyra.startApp({\n                MONGODB_URL: config.databaseUri,\n                API_URL: apiUrl,\n                BASE_URL: apiUrl + \"/modules/kaleyra\",\n                APPS_API_URL: apiUrl + \"/modules/apps\",\n                KALEYRA_API_URL: process.env.KALEYRA_API_URL,\n                API_KEY: process.env.API_KEY,\n                log: process.env.KALEYRA_LOG\n            }, () => {\n                winston.info(\"Tiledesk Kaleyra proxy server succesfully started.\");\n            })\n        }\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/messageActions/event/messageActionEvent.js",
    "content": "const EventEmitter = require('events');\n\nclass MessageActionEvent extends EventEmitter {}\n\nconst messageActionEvent = new MessageActionEvent();\n\n\n\nmodule.exports = messageActionEvent;\n"
  },
  {
    "path": "pubmodules/messageActions/index.js",
    "content": "const messageActionsInterceptor = require(\"./messageActionsInterceptor\");\nmodule.exports = {messageActionsInterceptor:messageActionsInterceptor};"
  },
  {
    "path": "pubmodules/messageActions/messageActionsInterceptor.js",
    "content": "\nconst messageEvent = require('../../event/messageEvent');\nconst botEvent = require('../../event/botEvent');\nvar messageActionEvent = require('./event/messageActionEvent');\nvar winston = require('../../config/winston');\nvar messageService = require('../../services/messageService');\nvar requestService = require('../../services/requestService');\nvar i8nUtil = require(\"../../utils/i8nUtil\");\nvar MessageConstants = require(\"../../models/messageConstants\");\nvar BotFromParticipant = require(\"../../utils/botFromParticipant\");\n\nclass MessageActionsInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"MessageActionsInterceptor listener start \");\n                    \n            //use .received to be sure for \\close the message is sent to chat21 and after that you can archive the conversation. otherwise a race condition occurs with message.create if \\close is sent by the bot      \n            messageEvent.on('message.received',  function(message) {          \n        // messageEvent.on('message.create',  function(message) {          \n            winston.debug(\"message.received \", message);\n\n                setImmediate(() => {\n                  \n                       if (message.text && message.text.indexOf(\"\\\\\")>-1) {\n                             winston.debug(\"message text contains command \");\n                             \n                             var start = message.text.indexOf(\"\\\\\");\n                             var end = message.text.length;\n                             \n                             var action = message.text.substring(start+1, end);\n                             winston.debug(\"message text contains action: \"+ action);\n                             messageActionEvent.emit(action, message);\n                             \n                             //messageActionEvent.emit(\"message.create\", message);\n                             \n                       }\n                    \n                });\n        });\n        \n        // messageActionEvent.on(\"start\", function(message) {\n        //     winston.info(\"called \\\\agent action\");\n             \n        //     var request = message.request;\n            \n            \n        //      //var botId = botEvent.getBotId(message);\n        //      var botId =  BotFromParticipant.getBotId(message);\n\n        //       winston.debug(\"botId: \" + botId);\n           \n              \n        //       if (!botId) {\n        //         if (request.availableAgents.length==0) {\n                    \n        //         }else {\n\n        //         }\n        //             messageService.send(\n        //                             'system', \n        //                             'Bot',                                     \n        //                             request.request_id,\n        //                             i8nUtil.getMessage(\"TOUCHING_OPERATOR\", request.language, MessageConstants.LABELS), \n        //                             request.id_project,\n        //                             'system', \n        //                             {\"updateconversation\" : false, messagelabel: {key: \"TOUCHING_OPERATOR\"}}\n        //                         );\n\n        //       }\n        // });\n\n        messageActionEvent.on(\"agent\", function(message) {\n            \n             winston.debug(\"called \\\\agent action\");\n             \n             var request = message.request;\n             \n\n             if (request) {\n                    //var botId = botEvent.getBotId(message);\n                var botId =  BotFromParticipant.getBotId(message);\n\n                winston.debug(\"botId: \" + botId);\n            \n                if (botId) {\n                    winston.debug(\"removing botId: bot_\" + botId);\n                    \n                    // removeParticipantByRequestId(request_id, id_project, member) \n                    //TODO USE FINALLY?\n                    //TODO you can use reroute?\n                //    requestService.removeParticipantByRequestId(request.request_id, request.id_project,\"bot_\"+botId ).then(function(removedRequest){\n                //     winston.debug(\"removeParticipantByRequestId: \", removedRequest);\n    \n                    // route(request_id, departmentid, id_project, nobot) {\n                        // se \\agent ma nessuno opertore online non toglie il bot \n                        requestService.route(request.request_id, request.department, request.id_project, true ).then(function(routedRequest) {\n                            winston.debug(\"routedRequest: \", routedRequest);\n\n                                // messageService.send(\n                                //     'system', \n                                //     'Bot',                                     \n                                //     request.request_id,\n                                //     i8nUtil.getMessage(\"TOUCHING_OPERATOR\", request.language, MessageConstants.LABELS), \n                                //     request.id_project,\n                                //     'system', \n                                //     {\"updateconversation\" : false, messagelabel: {key: \"TOUCHING_OPERATOR\"}}\n                                // );\n\n                        }).catch((err) => {\n                            winston.error(\"(MessageActionsInterceptor) route request error \", err)\n                        });\n                //    });\n                }   else {\n                //route(request_id, departmentid, id_project) {      \n                    //TODO USE FINALLY?\n\n                    requestService.reroute(request.request_id, request.id_project, true ).then(function() {\n\n                        // messageService.send(\n                        //     'system', \n                        //     'Bot',                                     \n                        //     request.request_id,\n                        //     i8nUtil.getMessage(\"TOUCHING_OPERATOR\", request.language, MessageConstants.LABELS), \n                        //     request.id_project,\n                        //     'system', \n                        //     {\"updateconversation\" : false, messagelabel: {key: \"TOUCHING_OPERATOR\"}}\n                        // );\n\n                    }).catch((err) => {\n                        winston.error(\"(MessageActionsInterceptor) reroute request error \", err)\n                    });\n                }   \n              \n             }\n                                                                                           \n                                \n                                \n        });\n\n\n\n        messageActionEvent.on(\"close\", function(message) {\n            \n            winston.verbose(\"called \\\\close action\");\n            \n            var request = message.request;\n            \n            if (request) {\n                // setTimeout(function() {\n                    // winston.info(\"delayed\")\n                    \n                    // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by)\n                    const closed_by = message.sender;\n                    requestService.closeRequestByRequestId(request.request_id, request.id_project, false, true, closed_by ).catch((err) => {\n                      winston.error(\"(MessageActionsInterceptor) closeRequestByRequestId error\", err);\n                    });\n                //   }, 1500);\n\n               \n            }\n                                                                                                                                                        \n       });\n        \n        /*\n       messageActionEvent.on(\"actions\", function(message) {\n        //    esegui custom action--->\n       });\n       messageActionEvent.on(\"events\", function(message) {\n        // lancia event\n    });*/\n\n       \n\n    }\n\n\n    \n}\n\nvar messageActionsInterceptor = new MessageActionsInterceptor();\nmodule.exports = messageActionsInterceptor;"
  },
  {
    "path": "pubmodules/messageTransformer/index.js",
    "content": "const messageTransformerInterceptor = require(\"./messageTransformerInterceptor\");\nconst microLanguageTransformerInterceptor = require(\"./microLanguageAttributesTransformerInterceptor\");\nconst messageHandlebarsTransformerInterceptor = require(\"./messageHandlebarsTransformerInterceptor\");\n\n\n\nmodule.exports = {messageTransformerInterceptor:messageTransformerInterceptor, \n                microLanguageTransformerInterceptor:microLanguageTransformerInterceptor,\n                messageHandlebarsTransformerInterceptor: messageHandlebarsTransformerInterceptor};"
  },
  {
    "path": "pubmodules/messageTransformer/messageHandlebarsTransformerInterceptor.js",
    "content": "\nconst messagePromiseEvent = require('../../event/messagePromiseEvent');\nconst Request = require('../../models/request');\nvar winston = require('../../config/winston');\nvar cacheUtil = require('../../utils/cacheUtil');\nvar handlebars = require('handlebars');\nvar cacheEnabler = require(\"../../services/cacheEnabler\");\n\nclass MessageHandlebarsTransformerInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"MessageHandlebarsTransformerInterceptor listener start \");\n \n\n        messagePromiseEvent.on('message.create.simple.before', async (data) => {\n            winston.debug('MessageHandlebarsTransformerInterceptor message.create.simple.before', data); \n\n            var message = data.beforeMessage;\n            \n            if (!message.text) { //for image i think\n                return data;\n            }\n            if (message.attributes && message.attributes.templateProcessor == true) { \n\n                // TODO if variables are presents\n\n                var q1 = Request.findOne({request_id:  message.recipient, id_project: message.id_project});\n\n                // if (message.attributes && message.attributes.populateTemplate == true) {\n                    q1.populate('lead').\n                    populate('department').  \n                    populate('participatingBots').\n                    populate('participatingAgents').       \n                    populate({path:'requester',populate:{path:'id_user'}});\n                // }\n\n                if (cacheEnabler.request) {\n                    q1.cache(cacheUtil.defaultTTL, message.id_project+\":requests:request_id:\"+message.recipient) //request_cache\n                    winston.debug('request cache enabled');\n                }\n\n                var request = await q1\n                    .exec();\n\n\n\n                var requestJSON = request.toJSON();\n\n                winston.debug('request mti: ', requestJSON);\n\n\n                var template = handlebars.compile(message.text);\n                winston.debug('template: '+ template);\n\n                // var templateSpec = handlebars.precompile(message.text);\n                // winston.info('templateSpec: ', templateSpec);\n\n\n                var replacements = {        \n                  request: requestJSON,\n                };\n            \n                // {{request.first_text}}\n                // {{request.participatingAgents.0.firstname}}\n\n\n                \n\n                var text = template(replacements);\n                winston.debug('text: '+ text);\n                message.text=text;\n\n            }\n            winston.debug('data: ' + JSON.stringify(data) );\n            return data;\n        });\n\n    }\n    \n}\n\nvar messageHandlebarsTransformerInterceptor = new MessageHandlebarsTransformerInterceptor();\nmodule.exports = messageHandlebarsTransformerInterceptor;"
  },
  {
    "path": "pubmodules/messageTransformer/messageTransformerInterceptor.js",
    "content": "\nconst messagePromiseEvent = require('../../event/messagePromiseEvent');\nconst labelService = require('../../services/labelService');\nconst Request = require('../../models/request');\nvar winston = require('../../config/winston');\nvar i8nUtil = require(\"../../utils/i8nUtil\");\nvar cacheUtil = require('../../utils/cacheUtil');\nvar cacheEnabler = require(\"../../services/cacheEnabler\");\n\n//TODO rename to LabelMessageTransformerInterceptor\nclass MessageTransformerInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"MessageTransformerInterceptor listener start \");\n \n\n        messagePromiseEvent.on('message.create.simple.before', async (data) => {\n            winston.debug('MessageTransformerInterceptor message.create.simple.before', data); \n\n            var message = data.beforeMessage;\n            \n            if (!message.text) { //for image i think\n                return data;\n            }\n\n            // https://stackoverflow.com/questions/413071/regex-to-get-string-between-curly-braces\n            var re = /\\${([^}]+)\\}/;\n            var m = message.text.match(re);\n            if (m != null) {\n                var messageExtracted = m[0].replace(re, '$1');\n                winston.debug('messageExtracted: '+messageExtracted);\n\n                var language = \"EN\";\n\n                let q = Request.findOne({request_id:  message.recipient, id_project: message.id_project});\n                    // populate('lead').\n                    // populate('department').  \n                    // populate('participatingBots').\n                    // populate('participatingAgents').       \n                    // populate({path:'requester',populate:{path:'id_user'}}).\n                    \n                if (cacheEnabler.request) {\n                    q.cache(cacheUtil.defaultTTL, message.id_project+\":requests:request_id:\"+message.recipient+\":simple\") //request_cache nocachepopulatereqired\n                    winston.debug('request cache enabled');\n                }\n\n\n                var request = await q.exec();\n              \n                winston.debug('request mti: ', request);\n\n                if (request && request.language) {\n                    language  = request.language.toUpperCase();\n                }\n\n\n\n                // if (message.language) {\n                //     language  = message.language.toUpperCase();\n                // }\n\n\n                // if (message.attributes && message.attributes.language) {\n                //     language  = message.attributes.language.toUpperCase();\n                // }\n\n                winston.debug('language: '+language);\n\n            // if (message.text.indexOf(\"${\")>-1) {\n            \n                // get a specific key of a project language merged with default (widget.json) but if not found return Pivot\n                var label = await labelService.get(message.id_project,language, messageExtracted);\n                winston.debug('MessageTransformerInterceptor label: ' + label);\n\n                if (label) {\n                    message.text=label;  //ATTENTION Changes is made by reference\n                }\n             \n                return data;\n                // labelService.getByLanguageAndKey(message.id_project, \"EN\", \"LABEL_PLACEHOLDER\").then(function(label) {\n                //     message.text=label;   \n                //     winston.info('MessageTransformerInterceptor return enter: '+ label);\n                //     return data;\n                // })\n                // winston.info('MessageTransformerInterceptor enter here???');\n            } else {\n                winston.debug('MessageTransformerInterceptor return');\n                return data;\n            }\n           \n          });\n\n\n    }\n\n\n    \n}\n\nvar messageTransformerInterceptor = new MessageTransformerInterceptor();\nmodule.exports = messageTransformerInterceptor;"
  },
  {
    "path": "pubmodules/messageTransformer/microLanguageAttributesTransformerInterceptor.js",
    "content": "\nconst messagePromiseEvent = require('../../event/messagePromiseEvent');\n\nvar winston = require('../../config/winston');\nconst { TiledeskChatbotUtil } = require('@tiledesk/tiledesk-chatbot-util');\n\n\nclass MicroLanguageTransformerInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"MicroLanguageTransformerInterceptor listener start \");\n        \n        messagePromiseEvent.on('message.create.simple.before', async (data) => {\n            winston.debug('MicroLanguageTransformerInterceptor message.create.simple.before', data); \n\n            var message = data.beforeMessage;\n            \n            if (!message.text) { //for image i think\n                return data;\n            }\n\n            if (message.attributes && message.attributes.microlanguage == true) { \n                var reply = TiledeskChatbotUtil.parseReply(message.text);\n                winston.debug('parseReply: ' + JSON.stringify(reply) );\n                var messageReply = reply.message;\n\n                 \n                var msg_attributes = {\"_raw_message\": message.text};\n\n                if (messageReply && messageReply.attributes) {\n                    for(const [key, value] of Object.entries(messageReply.attributes)) {\n                        msg_attributes[key] = value\n                    }\n                }\n\n                messageReply.attributes = msg_attributes;\n              \n        \n                //data.beforeMessage = messageReply; //https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language\n                // message = messageReply;\n                message.text = messageReply.text;                  //ATTENTION Changes is made by reference\n                message.attributes = messageReply.attributes;      //ATTENTION Changes is made by reference\n                message.type = messageReply.type;                  //ATTENTION Changes is made by reference\n                message.metadata = messageReply.metadata;          //ATTENTION Changes is made by reference\n                // message.metadata = messageReply.metadata;\n\n                //data.beforeMessage = messageReply;\n               \n             }\n             winston.debug('data: ' + JSON.stringify(data) );\n             return data;\n            \n        });\n\n\n    }\n\n    \n}\n\nvar microLanguageTransformerInterceptor = new MicroLanguageTransformerInterceptor();\nmodule.exports = microLanguageTransformerInterceptor;"
  },
  {
    "path": "pubmodules/messageTransformer/microLanguageTransformerInterceptor.js",
    "content": "\nconst messagePromiseEvent = require('../../event/messagePromiseEvent');\n\nvar winston = require('../../config/winston');\nconst { TiledeskChatbotUtil } = require('@tiledesk/tiledesk-chatbot-util');\n\n\nclass MicroLanguageTransformerInterceptor {\n\n \n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"MicroLanguageTransformerInterceptor listener start \");\n        \n        messagePromiseEvent.on('message.create.simple.before', async (data) => {\n            winston.info('MicroLanguageTransformerInterceptor message.create.simple.before', data); \n\n            var message = data.beforeMessage;\n            \n            if (!message.text) { //for image i think\n                return data;\n            }\n\n            if (message.attributes && message.attributes.microlanguage == true) { \n                var reply = TiledeskChatbotUtil.parseReply(message.text);\n                winston.info('parseReply: ' + JSON.stringify(reply) );\n                var messageReply = reply.message;\n\n                 \n                var msg_attributes = {\"_raw_message\": message.text};\n\n                if (messageReply && messageReply.attributes) {\n                    for(const [key, value] of Object.entries(messageReply.attributes)) {\n                        msg_attributes[key] = value\n                    }\n                }\n\n                messageReply.attributes = msg_attributes;\n              \n        \n                //data.beforeMessage = messageReply; //https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language\n                // message = messageReply;\n                message.text = messageReply.text;                  //ATTENTION Changes is made by reference\n                message.attributes = messageReply.attributes;      //ATTENTION Changes is made by reference\n                message.type = messageReply.type;                  //ATTENTION Changes is made by reference\n                message.metadata = messageReply.metadata;          //ATTENTION Changes is made by reference\n                // message.metadata = messageReply.metadata;\n\n                //data.beforeMessage = messageReply;\n               \n             }\n             winston.debug('data: ' + JSON.stringify(data) );\n             return data;\n            \n        });\n\n\n    }\n\n    \n}\n\nvar microLanguageTransformerInterceptor = new MicroLanguageTransformerInterceptor();\nmodule.exports = microLanguageTransformerInterceptor;"
  },
  {
    "path": "pubmodules/messenger/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst messenger = require(\"@tiledesk/tiledesk-messenger-connector\");\nconst messengerRoute = messenger.router;\n\n\nmodule.exports = { listener: listener, messengerRoute: messengerRoute }"
  },
  {
    "path": "pubmodules/messenger/listener.js",
    "content": "const messenger = require(\"@tiledesk/tiledesk-messenger-connector\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\nconst mongoose = require(\"mongoose\");\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\n//winston.info('Messenger apiUrl: ' + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"Messenger Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"messenger config databaseUri: \" + config.databaseUri);\n        }\n\n        let graph_url = process.env.META_GRAPH_URL || config.graphUrl || \"https://graph.facebook.com/v14.0/\"\n        winston.debug(\"Messenger graph_url: \" + graph_url);\n\n        let log = process.env.MESSENGER_LOG || false\n        winston.debug(\"Messenger log: \" + log);\n\n        let fb_app_id = process.env.FB_APP_ID;\n        winston.debug(\"Messenger fb_app_id: \", fb_app_id);\n\n        let fb_app_secret = process.env.FB_APP_SECRET;\n        winston.debug(\"Messenger fb_app_secret: \", fb_app_secret);\n        \n        let fb_verify_token = process.env.MESSENGER_VERIFY_TOKEN;\n\n        let dashboard_base_url = process.env.EMAIL_BASEURL || config.baseUrl;\n        winston.debug(\"Messenger dashboard_base_url: \", dashboard_base_url);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME\n        }\n\n\n        messenger.startApp({\n            MONGODB_URL: config.databaseUri,   \n            dbconnection: dbConnection,      \n            API_URL: apiUrl,\n            BASE_URL: apiUrl + \"/modules/messenger\",\n            APPS_API_URL: apiUrl + \"/modules/apps\",\n            FB_APP_ID: fb_app_id,\n            FB_APP_SECRET: fb_app_secret,\n            GRAPH_URL: graph_url,\n            DASHBOARD_BASE_URL: dashboard_base_url,\n            VERIFY_TOKEN: fb_verify_token,\n            BRAND_NAME: brand_name,\n            log: log\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Tiledesk Messenger Connector proxy server succesfully started.\");\n            } else {\n                winston.info(\"unable to start Tiledesk Messenger Connector. \" + err);\n            }\n        })\n\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/mqttTest/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst mqttTest = require(\"@tiledesk/tiledesk-client/mqtt-route\");\nconst mqttTestRoute = mqttTest.router;\n\n\nmodule.exports = { listener: listener, mqttTestRoute: mqttTestRoute }"
  },
  {
    "path": "pubmodules/mqttTest/listener.js",
    "content": "const mqttTest = require(\"@tiledesk/tiledesk-client/mqtt-route\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('MqttTest apiUrl: ' + apiUrl);\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"MqttTest Listener listen\");\n    \n        let log = process.env.MQTT_TEST_LOG || false;\n        winston.info(\"mqtt log: \" + log);\n\n        mqttTest.startApp({\n            LOG_STATUS: log\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Tiledesk mqtt-test succesfully started.\");\n            } else {\n                winston.info(\"unable to start Tiledesk mqtt-test. \" + err);\n            }\n        })\n\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/pubModulesManager.js",
    "content": "\nvar winston = require('../config/winston');\nvar validtoken = require('../middleware/valid-token');\nvar roleChecker = require('../middleware/has-role');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\n\nclass PubModulesManager {\n\n    constructor() {\n        this.appRules = undefined;\n        this.messageActions = undefined;\n        this.emailNotification = undefined;\n        \n        this.eventsRoute = undefined;\n        this.entityEraser = undefined;\n        this.messageTransformer = undefined;\n\n        this.scheduler = undefined;\n\n        this.rasa = undefined;\n        this.rasaRoute = undefined;\n\n        this.apps = undefined;\n        this.appsRoute = undefined;\n\n        this.whatsapp = undefined;\n        this.whatsappRoute = undefined;\n\n        this.messenger = undefined;\n        this.messengerRoute = undefined;\n\n        this.telegram = undefined;\n        this.telegramRoute = undefined;\n\n        this.sms = undefined;\n        this.smsRoute = undefined;\n        \n        this.voice = undefined;\n        this.voiceRoute = undefined;\n\n        this.voiceTwilio = undefined;\n        this.voiceTwilioRoute = undefined;\n\n        this.mqttTest = undefined;\n        this.mqttTestRoute = undefined;\n\n        this.templates = undefined;\n        this.templatesRoute = undefined;\n\n        this.kaleyra = undefined;\n        this.kaleyraRoute = undefined;\n\n        this.activityArchiver = undefined;\n        this.activityRoute = undefined;\n\n        this.analyticsRoute = undefined;\n\n        this.cannedResponseRoute = undefined;\n\n        this.trigger = undefined;\n        this.triggerRoute = undefined;\n\n        this.tilebot = undefined;\n        this.tilebotRoute = undefined;\n\n        this.queue = undefined;\n\n        this.jobsManager = undefined;\n\n        this.routingQueue = undefined;\n        this.routingQueueQueued = undefined;\n\n        this.cache = undefined;\n\n        this.dialogFlow = undefined;\n    }\n\n  \n\n    use(app) {\n        \n        if (this.rasaRoute) {\n            app.use('/modules/rasa', this.rasaRoute);\n            winston.info(\"PubModulesManager rasaRoute controller loaded\");       \n        }\n        if (this.appsRoute) {\n            app.use('/modules/apps', this.appsRoute);\n            winston.info(\"PubModulesManager appsRoute controller loaded\");       \n        }\n        if (this.whatsappRoute) {\n            app.use('/modules/whatsapp', this.whatsappRoute);\n            winston.info(\"PubModulesManager whatsappRoute controller loaded\");\n        }\n        if (this.messengerRoute) {\n            app.use('/modules/messenger', this.messengerRoute);\n            winston.info(\"PubModulesManager messengerRoute controller loaded\");\n        }\n        if (this.telegramRoute) {\n            app.use('/modules/telegram', this.telegramRoute);\n            winston.info(\"PubModulesManager telegramRoute controller loaded\");\n        }\n        if (this.smsRoute) {\n            app.use('/modules/sms', this.smsRoute);\n            winston.info(\"PubModulesManager smsRoute controller loaded\");\n        }\n        if (this.voiceRoute) {\n            app.use('/modules/voice', this.voiceRoute);\n            winston.info(\"PubModulesManager voiceRoute controller loaded\");\n        }\n        if (this.voiceTwilioRoute) {\n            app.use('/modules/voice-twilio', this.voiceTwilioRoute);\n            winston.info(\"PubModulesManager voiceTwilioRoute controller loaded\");\n        }\n        if (this.mqttTestRoute) {\n            app.use('/modules/mqttTest', this.mqttTestRoute);\n            winston.info(\"PubModulesManager mqttTestRoute controller loaded\");\n        }\n        if (this.templatesRoute) {\n            app.use('/modules/templates', this.templatesRoute);\n            winston.info(\"PubModulesManager templatesRoute controller loaded\");\n        }\n        if (this.kaleyraRoute) {\n            app.use('/modules/kaleyra', this.kaleyraRoute);\n            winston.info(\"PubModulesManager kaleyraRoute controller loaded\");\n        }\n        if (this.tilebotRoute) {\n            app.use('/modules/tilebot', this.tilebotRoute);\n            winston.info(\"PubModulesManager tilebot controller loaded\");       \n        }\n\n        if (this.dialogFlow) {\n            app.use(\"/modules/dialogFlow\", this.dialogFlow.dialogflowRoute);\n            winston.info(\"PubModulesManager dialogFlow controller loaded\");       \n        }\n\n    }\n    useUnderProjects(app) {\n        var that = this;\n        winston.debug(\"PubModulesManager using controllers\");     \n\n\n        //  dario riesce con jwt custom di progettto a a scrivere events di progetto b\n        if (this.eventsRoute) {\n            app.use('/:projectid/events', this.eventsRoute);\n            // app.use('/:projectid/events', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('guest')], this.eventsRoute);\n            winston.info(\"ModulesManager eventsRoute controller loaded\");       \n        }\n        \n\n        if (this.activityRoute) {\n            app.use('/:projectid/activities', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], this.activityRoute);\n             winston.info(\"ModulesManager activities controller loaded\");       \n        }\n\n        if (this.analyticsRoute) {\n            app.use('/:projectid/analytics', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], this.analyticsRoute);\n             winston.info(\"ModulesManager analytics controller loaded\");       \n         }\n\n         if (this.cannedResponseRoute) {            \n            app.use('/:projectid/canned', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], this.cannedResponseRoute);\n            winston.info(\"ModulesManager canned controller loaded\");       \n        }\n\n        if (this.triggerRoute) {\n            app.use('/:projectid/modules/triggers', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], this.triggerRoute);\n            winston.info(\"ModulesManager trigger controller loaded\");       \n        }\n\n    }\n\n   \n    init(config) {\n        winston.debug(\"PubModulesManager init\");\n\n        this.jobsManager = config.jobsManager;\n        this.tdCache = config.tdCache;\n\n        try {\n            this.appRules = require('./rules/appRules');\n            // this.appRules.start();\n            winston.info(\"PubModulesManager initialized rules.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init rules module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init rules module\", err);\n            }\n        }\n\n        try {\n            this.messageActions = require('./messageActions');\n            winston.debug(\"this.messageActions:\"+ this.messageActions);\n            // this.messageActions.messageActionsInterceptor.listen();\n            winston.info(\"PubModulesManager initialized messageActions.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init messageActions module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init messageActions module\", err);\n            }\n        }\n\n        try {\n            this.messageTransformer = require('./messageTransformer');\n            winston.debug(\"this.messageTransformer:\"+ this.messageTransformer);\n            // this.messageTransformer.messageTransformerInterceptor.listen();\n            // this.messageTransformer.microLanguageTransformerInterceptor.listen();\n            winston.info(\"PubModulesManager initialized messageTransformer.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init messageTransformer module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init messageTransformer module\", err);\n            }\n        }\n        \n        \n         try {\n            this.emailNotification = require('./emailNotification');\n            winston.debug(\"this.emailNotification:\"+ this.emailNotification);\n            // this.emailNotification.requestNotification.listen();\n            winston.info(\"PubModulesManager initialized requestNotification loaded.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init emailNotification module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init emailNotification module\", err);\n            }\n        }\n        \n\n        try {           \n            this.eventsRoute = require('./events/eventRoute');\n            winston.debug(\"this.eventRoute:\"+ this.eventsRoute);          \n            winston.info(\"PubModulesManager initialized eventsRoute.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init eventsRoute module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init eventsRoute module\", err);\n            }\n        }\n\n        \n        try {\n            this.entityEraser = require('./entityEraser');\n            winston.debug(\"this.entityEraser:\"+ this.entityEraser);\n            // this.entityEraser.eraserInterceptor.listen();\n            winston.info(\"PubModulesManager initialized entityEraser.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init entityEraser module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init entityEraser module\", err);\n            }\n        }\n\n\n\n        try {\n            this.scheduler = require('./scheduler');\n            winston.debug(\"this.scheduler:\"+ this.scheduler);    \n            // this.scheduler.taskRunner.start();        \n            winston.info(\"PubModulesManager initialized scheduler.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init scheduler module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init scheduler module\", err);\n            }\n        }\n\n\n        try {\n            this.rasa = require('./rasa');\n            winston.debug(\"this.rasa:\"+ this.rasa);    \n            this.rasa.listener.listen(config);      \n\n            this.rasaRoute = this.rasa.rasaRoute;\n\n            winston.info(\"PubModulesManager initialized rasa.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init rasa module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init rasa module\", err);\n            }\n        }\n\n        try {\n            this.apps = require('./apps');\n            winston.debug(\"this.apps: \" + this.apps);\n            this.apps.listener.listen(config);\n\n            this.appsRoute = this.apps.appsRoute;\n\n            winston.info(\"PubModulesManager initialized apps.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init apps module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.whatsapp = require('./whatsapp');\n            winston.debug(\"this.whatsapp: \" + this.whatsapp);\n            this.whatsapp.listener.listen(config);\n\n            this.whatsappRoute = this.whatsapp.whatsappRoute;\n\n            winston.info(\"PubModulesManager initialized apps.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init apps module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.messenger = require('./messenger');\n            winston.debug(\"this.messenger: \" + this.messenger);\n            this.messenger.listener.listen(config);\n\n            this.messengerRoute = this.messenger.messengerRoute;\n\n            winston.info(\"PubModulesManager initialized apps.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init apps module not found \");\n            }else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.telegram = require('./telegram');\n            winston.info(\"this.telegram: \" + this.telegram);\n            this.telegram.listener.listen(config);\n\n            this.telegramRoute = this.telegram.telegramRoute;\n\n            winston.info(\"PubModulesManager initialized apps (telegram).\")\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init apps module not found \");\n            } else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        if (process.env.VOICE_TOKEN === process.env.VOICE_SECRET) {\n            try {\n                this.voice = require('./voice');\n                winston.info(\"this.voice: \" + this.voice);\n                this.voice.listener.listen(config);\n    \n                this.voiceRoute = this.voice.voiceRoute;\n    \n                winston.info(\"PubModulesManager initialized apps (voice).\")\n            } catch(err) {\n                console.log(\"\\n Unable to start voice connector: \", err);\n                if (err.code == 'MODULE_NOT_FOUND') {\n                    winston.info(\"PubModulesManager init apps module not found \");\n                } else {\n                    winston.info(\"PubModulesManager error initializing init apps module\", err);\n                }\n            }\n        }\n\n        if (process.env.VOICE_TWILIO_TOKEN === process.env.VOICE_TWILIO_SECRET) {\n            try {\n                this.voiceTwilio = require('./voice-twilio');\n                winston.info(\"this.voiceTwilio: \" + this.voiceTwilio);\n                this.voiceTwilio.listener.listen(config);\n\n                this.voiceTwilioRoute = this.voiceTwilio.voiceTwilioRoute;\n\n                winston.info(\"PubModulesManager initialized apps (voiceTwilio).\")\n            } catch(err) {\n                console.log(\"\\n Unable to start voiceTwilio connector: \", err);\n                if (err.code == 'MODULE_NOT_FOUND') {\n                    winston.info(\"PubModulesManager init apps module not found \");\n                } else {\n                    winston.info(\"PubModulesManager error initializing init apps module\", err);\n                }\n            }\n        }\n\n        try {\n            this.sms = require('./sms');\n            winston.info(\"this.sms: \" + this.sms);\n            this.sms.listener.listen(config);\n\n            this.smsRoute = this.sms.smsRoute;\n\n            winston.info(\"PubModulesManager initialized apps (sms).\")\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init apps module not found \");\n            } else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.mqttTest = require('./mqttTest');\n            winston.info(\"this.mqttTest: \" + this.mqttTest);\n            this.mqttTest.listener.listen(config);\n\n            this.mqttTestRoute = this.mqttTest.mqttTestRoute;\n\n            winston.info(\"PubModulesManager initialized apps (mqttTest).\")\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init apps module not found \", err);\n            } else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.templates = require('./chatbotTemplates');\n            winston.info(\"this.templates: \" + this.templates);\n            this.templates.listener.listen(config);\n\n            this.templatesRoute = this.templates.templatesRoute;\n\n            winston.info(\"PubModulesManager initialized apps (templates).\")\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init apps module not found \");\n            } else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n        \n        try {\n            this.kaleyra = require('./kaleyra');\n            winston.debug(\"this.kaleyra: \" + this.kaleyra);\n            this.kaleyra.listener.listen(config);\n\n            this.kaleyraRoute = this.kaleyra.kaleyraRoute;\n\n            winston.info(\"PubModulesManager initialized apps.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init apps module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init apps module\", err);\n            }\n        }\n\n        try {\n            this.activityArchiver = require('./activities').activityArchiver;\n            // this.activityArchiver.listen();\n            winston.debug(\"this.activityArchiver:\"+ this.activityArchiver);   \n            \n            this.activityRoute = require('./activities').activityRoute;\n            winston.debug(\"this.activityRoute:\"+ this.activityRoute);\n\n            winston.info(\"ModulesManager activities initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init activities module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init activities module\", err);\n            }\n        }\n\n\n        try {\n            this.analyticsRoute = require('./analytics').analyticsRoute;\n            winston.debug(\"this.analyticsRoute:\"+ this.analyticsRoute);        \n            winston.info(\"ModulesManager analyticsRoute initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init analytics module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init analytics module\", err);\n            }\n        }\n\n\n\n        try {\n            this.cannedResponseRoute = require('./canned').cannedResponseRoute;\n            winston.debug(\"this.cannedResponseRoute:\"+ this.cannedResponseRoute);        \n            winston.info(\"ModulesManager cannedResponseRoute initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init canned module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init canned module\", err);\n            }\n        }\n\n      \n        try {\n            this.trigger = require('./trigger').start;\n            winston.debug(\"this.trigger:\"+ this.trigger);\n            this.triggerRoute = require('./trigger').triggerRoute;\n            winston.debug(\"this.triggerRoute:\"+ this.triggerRoute);       \n            winston.info(\"ModulesManager trigger initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init trigger module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init trigger module\", err);\n            }\n        }\n        \n\n\n\n        try {\n            this.tilebot = require('./tilebot');\n            winston.debug(\"this.tilebot:\"+ this.tilebot);    \n            this.tilebot.listener.listen(config);      \n            this.tilebotRoute = this.tilebot.tilebotRoute;\n\n            winston.info(\"PubModulesManager initialized tilebot.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init tilebot module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init tilebot module\", err);\n            }\n        }\n\n\n\n\n        try {\n            this.queue = require('./queue');\n            winston.debug(\"this.queue:\"+ this.queue);                \n\n            winston.info(\"PubModulesManager initialized queue.\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') { \n                winston.info(\"PubModulesManager init queue module not found\");\n            }else {\n                winston.info(\"PubModulesManager error initializing init queue module\", err);\n            }\n        }\n\n\n\n\n        try {\n            this.routingQueue = require('./routing-queue').listener;\n            this.routingQueueQueued = require('./routing-queue').listenerQueued;\n\n            // this.routingQueue.listen();\n            winston.debug(\"this.routingQueue:\"+ this.routingQueue);           \n            winston.debug(\"this.routingQueueQueued:\"+ this.routingQueueQueued);           \n\n            winston.info(\"PubModulesManager routing queue initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init routing queue module not found\");\n            }else {\n                winston.error(\"PubModulesManager error initializing init routing queue module\", err);\n            }\n        }\n\n\n        try {            \n            this.cache = require('./cache').cachegoose(config.mongoose);            \n            winston.debug(\"this.cache:\"+ this.cache);           \n            winston.info(\"PubModulesManager cache initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init cache module not found\");\n            }else {\n                winston.error(\"PubModulesManager error initializing init cache module\", err);\n            } \n        }\n\n        \n\n        try {\n            this.dialogFlow = require('./dialogflow');\n            winston.debug(\"this.dialogFlow:\"+ this.dialogFlow);           \n            this.dialogFlow.listener.listen(config);\n            winston.info(\"PubModulesManager dialogFlow  initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"PubModulesManager init dialogFlow module not found\");\n            }else {\n                winston.error(\"PubModulesManager error initializing init dialogFlow module\", err);\n            }\n        }\n    }\n\n    start() {\n        if (this.appRules) {\n            try {\n                this.appRules.start();\n                winston.info(\"PubModulesManager appRules started.\");   \n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting appRules module\", err);            \n            }\n        }\n        \n        if (this.messageActions) {\n            try {\n                this.messageActions.messageActionsInterceptor.listen();\n                winston.info(\"PubModulesManager messageActions started.\");   \n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting messageActions module\", err);            \n            }\n        }\n        \n\n        if (this.messageTransformer) {\n            try {\n                this.messageTransformer.messageTransformerInterceptor.listen();\n                this.messageTransformer.microLanguageTransformerInterceptor.listen();    \n                this.messageTransformer.messageHandlebarsTransformerInterceptor.listen();\n                winston.info(\"PubModulesManager messageTransformer started.\");   \n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting messageTransformer module\", err);            \n            }\n            \n        }\n        \n        // job_here\n        if (this.emailNotification) {\n            try {\n                if (this.tdCache) {\n                    this.emailNotification.requestNotification.setTdCache(this.tdCache);\n                }\n                // this.emailNotification.requestNotification.listen();\n                this.jobsManager.listenEmailNotification(this.emailNotification);\n                winston.info(\"PubModulesManager emailNotification started.\");   \n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting requestNotification module\", err);            \n            }\n        }\n        \n        if (this.scheduler) {\n            try {\n                // this.scheduler.taskRunner.start();     \n                this.jobsManager.listenScheduler(this.scheduler);\n                winston.info(\"PubModulesManager scheduler started.\");   \n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting scheduler module\", err);            \n            }\n        } \n\n        // job_here\n        if (this.activityArchiver) {\n            try {\n                // this.activityArchiver.listen();\n                this.jobsManager.listenActivityArchiver(this.activityArchiver);\n                winston.info(\"PubModulesManager activityArchiver started\");\n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting activityArchiver module\", err);            \n            }\n        }\n\n\n        if (this.routingQueue) {\n            try {\n                this.routingQueue.listen();  \n                winston.info(\"PubModulesManager routingQueue started\");\n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting routingQueue module\", err);            \n            }\n        }\n        if (this.routingQueueQueued) {\n            try {\n                // this.routingQueueQueued.listen();  \n                this.jobsManager.listenRoutingQueue(this.routingQueueQueued);\n                winston.info(\"PubModulesManager routingQueue queued started\");\n            } catch(err) {        \n                winston.info(\"PubModulesManager error starting routingQueue queued module\", err);            \n            }\n        }\n\n        // if (this.dialogFlow) {\n        //     try {\n        //         this.dialogFlow.listen();\n        //         winston.info(\"PubModulesManager dialogFlow started\");\n        //     } catch(err) {        \n        //         winston.info(\"PubModulesManager error starting dialogFlow module\", err);            \n        //     }\n        // }\n\n\n    }\n\n\n     \n}\n\nvar pubModulesManager = new PubModulesManager();\nmodule.exports = pubModulesManager;\n"
  },
  {
    "path": "pubmodules/queue/index.js",
    "content": "const reconnect = require(\"./reconnect\");\nconst reconnectFanout = require(\"./reconnectFanout\");\n\nmodule.exports = {reconnect:reconnect,reconnectFanout: reconnectFanout };"
  },
  {
    "path": "pubmodules/queue/reconnect.js",
    "content": "var amqp = require('amqplib/callback_api');\nvar winston = require('../../config/winston');\nconst requestEvent = require('../../event/requestEvent');\nconst messageEvent = require('../../event/messageEvent');\nconst leadEvent = require('../../event/leadEvent');\n\nconst botEvent = require('../../event/botEvent');\nconst authEvent = require('../../event/authEvent');\n// https://elements.heroku.com/addons/cloudamqp\n// https://gist.github.com/carlhoerberg/006b01ac17a0a94859ba#file-reconnect-js\n// http://www.rabbitmq.com/tutorials/tutorial-one-javascript.html\n\n// if the connection is closed or fails to be established at all, we will reconnect\nvar amqpConn = null;\n\nvar url = process.env.CLOUDAMQP_URL + \"?heartbeat=60\" || \"amqp://localhost\";\n// attento devi aggiornare configMap di PRE E PROD\n// var url = process.env.AMQP_URL + \"?heartbeat=60\" || \"amqp://localhost?heartbeat=60\";\n\n// var durable = true;\nvar durable = false;\n\n// if (process.env.ENABLE_DURABLE_QUEUE == false || process.env.ENABLE_DURABLE_QUEUE == \"false\") {\n//   durable = false;\n// }\n\nvar persistent = false;\nif (process.env.ENABLE_PERSISTENT_QUEUE == true || process.env.ENABLE_PERSISTENT_QUEUE == \"true\") {\n  persistent = true;\n}\n\nvar exchange = process.env.QUEUE_EXCHANGE_TOPIC || 'amq.topic';\n\nvar queueName = process.env.QUEUE_NAME || 'jobs';\nwinston.info(\"Durable queue: \" + durable + \" Persistent queue: \" + persistent + \" Exchange topic: \" + exchange+ \" Queue name: \" + queueName);\n\n\nfunction start() {\n  amqp.connect(url, function(err, conn) {\n    if (err) {\n      winston.error(\"[AMQP]\", err);\n      return setTimeout(start, 1000);\n    }\n    conn.on(\"error\", function(err) {\n      if (err.message !== \"Connection closing\") {\n        winston.error(\"[AMQP] conn error\", err);\n      }\n    });\n    conn.on(\"close\", function() {\n      winston.error(\"[AMQP] reconnecting\");\n      return setTimeout(start, 1000);\n    });\n\n    winston.info(\"[AMQP] connected\");\n    amqpConn = conn;\n\n    whenConnected();\n  });\n}\n\nfunction whenConnected() {\n  startPublisher();\n\n\n  let jobWorkerEnabled = false;\n  if (process.env.JOB_WORKER_ENABLED==\"true\" || process.env.JOB_WORKER_ENABLED == true) {\n      jobWorkerEnabled = true;\n  }\n  winston.info(\"JobsManager jobWorkerEnabled: \"+ jobWorkerEnabled);  \n\n  if (jobWorkerEnabled == false) {\n    winston.info(\"Queue Reconnect starts queue worker (queue observer)\");\n    startWorker();\n  } else {\n    winston.info(\"Queue Reconnect without queue worker (queue observer) because external worker is enabled\");\n  }\n  \n}\n\nvar pubChannel = null;\nvar offlinePubQueue = [];\nfunction startPublisher() {\n  amqpConn.createConfirmChannel(function(err, ch) {\n    if (closeOnErr(err)) return;\n    ch.on(\"error\", function(err) {\n      winston.error(\"[AMQP] channel error\", err);\n    });\n    ch.on(\"close\", function() {\n      winston.info(\"[AMQP] channel closed\");\n    });\n\n    pubChannel = ch;\n    while (true) {\n      var m = offlinePubQueue.shift();\n      if (!m) break;\n      publish(m[0], m[1], m[2]);\n    }\n  });\n}\n\n// method to publish a message, will queue messages internally if the connection is down and resend later\nfunction publish(exchange, routingKey, content) {\n  try {\n    pubChannel.publish(exchange, routingKey, content, { persistent: persistent },\n      // pubChannel.publish(exchange, routingKey, content, { persistent: true },\n\n                       function(err, ok) {\n                         if (err) {\n                          winston.error(\"[AMQP] publish\", err);\n                           offlinePubQueue.push([exchange, routingKey, content]);\n                           pubChannel.connection.close();\n                         }\n                       });\n  } catch (e) {\n    winston.error(\"[AMQP] publish\", e);\n    offlinePubQueue.push([exchange, routingKey, content]);\n  }\n}\n\n// A worker that acks messages only if processed succesfully\n// var channel;\nfunction startWorker() {\n    amqpConn.createChannel(function(err, ch) {\n      if (closeOnErr(err)) return;\n      ch.on(\"error\", function(err) {\n        winston.error(\"[AMQP] channel error\", err);\n      });\n      ch.on(\"close\", function() {\n        winston.info(\"[AMQP] channel closed\");\n      });\n      ch.prefetch(10);//leggila da env\n      ch.assertExchange(exchange, 'topic', {\n        durable: durable\n        // durable: true\n      });\n\n      ch.assertQueue(queueName, { durable: durable }, function(err, _ok) {\n      // ch.assertQueue(\"jobs\", { durable: true }, function(err, _ok) {\n        if (closeOnErr(err)) return;\n        ch.bindQueue(_ok.queue, exchange, \"request_create\", {}, function(err3, oka) {\n            winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_create\");\n            winston.info(\"Data queue\", oka)\n        });\n        ch.bindQueue(_ok.queue, exchange, \"request_update_preflight\", {}, function(err3, oka) {\n            winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_update_preflight\");\n            winston.info(\"Data queue\", oka)\n        });\n        ch.bindQueue(_ok.queue, exchange, \"request_participants_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_participants_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_participants_join\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_participants_join\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_participants_leave\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_participants_leave\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_close\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_close\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_close_extended\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_close_extended\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_workingStatus_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_workingStatus_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"message_create\", {}, function(err3, oka) {\n              winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: message_create\");\n              winston.info(\"Data queue\", oka)\n        });\n        ch.bindQueue(_ok.queue, exchange, \"project_user_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: project_user_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"faqbot_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: faqbot_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"lead_create\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: lead_create\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"lead_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: lead_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"lead_fullname_email_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: lead_fullname_email_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n        ch.bindQueue(_ok.queue, exchange, \"request_snapshot_update\", {}, function(err3, oka) {\n          winston.info(\"Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_snapshot_update\");\n          winston.info(\"Data queue\", oka)\n        });\n\n\n        ch.consume(queueName, processMsg, { noAck: false });\n        winston.info(\"Worker is started\");\n      });\n  \n\n    function processMsg(msg) {\n      work(msg, function(ok) {\n        try {\n          if (ok)\n            ch.ack(msg);\n          else\n            ch.reject(msg, true);\n        } catch (e) {\n          closeOnErr(e);\n        }\n      });\n    }\n  });\n}\n\nfunction work(msg, cb) {\n  const message_string = msg.content.toString();\n  const topic = msg.fields.routingKey //.replace(/[.]/g, '/');\n\n  winston.debug(\"Got msg topic:\" + topic);\n\n  winston.debug(\"Got msg:\"+ message_string +  \" topic:\" + topic);\n\n  if (topic === 'request_create') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.create.queue', msg.content);\n    requestEvent.emit('request.create.queue', JSON.parse(message_string));\n  }\n  if (topic === 'request_update') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.update.queue',  JSON.parse(message_string));\n  }\n\n  if (topic === 'request_update_preflight') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.update.preflight.queue',  JSON.parse(message_string));\n  }    \n\n  if (topic === 'request_participants_join') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.participants.join.queue',  JSON.parse(message_string));\n  }   \n\n\n  if (topic === 'request_participants_leave') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.participants.leave.queue',  JSON.parse(message_string));\n  }   \n  \n  if (topic === 'request_participants_update') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.participants.update.queue',  JSON.parse(message_string));\n  }   \n  \n  if (topic === 'request_close') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.close.queue',  JSON.parse(message_string));\n  }     \n  \n  if (topic === 'request_close_extended') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    requestEvent.emit('request.close.extended.queue',  JSON.parse(message_string));\n  }     \n\n  if (topic === 'request_workingStatus_update') {\n    winston.debug(\"reconnect here topic:\" + topic); \n    requestEvent.emit('request.workingStatus.update.queue',  JSON.parse(message_string));\n  }     \n\n  if (topic === 'message_create') {\n    winston.debug(\"reconnect here topic:\" + topic);\n    // requestEvent.emit('request.create.queue', msg.content);\n    messageEvent.emit('message.create.queue', JSON.parse(message_string));\n  }\n  if (topic === 'project_user_update') {\n    winston.debug(\"reconnect here topic:\" + topic);\n    // requestEvent.emit('request.create.queue', msg.content);\n    authEvent.emit('project_user.update.queue', JSON.parse(message_string));\n  }\n\n  if (topic === 'faqbot_update') {\n    winston.debug(\"reconnect here topic faqbot_update:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    botEvent.emit('faqbot.update.queue',  JSON.parse(message_string));\n  }\n\n  if (topic === 'lead_create') {\n    winston.debug(\"reconnect here topic lead_create:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    leadEvent.emit('lead.create.queue',  JSON.parse(message_string));\n  }\n\n  if (topic === 'lead_update') {\n    winston.debug(\"reconnect here topic lead_update:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    leadEvent.emit('lead.update.queue',  JSON.parse(message_string));\n  }\n\n  if (topic === 'lead_fullname_email_update') {\n    winston.debug(\"reconnect here topic lead_fullname_email_update:\" + topic); \n    // requestEvent.emit('request.update.queue',  msg.content);\n    leadEvent.emit('lead.fullname.email.update.queue',  JSON.parse(message_string));\n  }\n\n  if (topic === 'request_snapshot_update') {\n    winston.debug(\"reconnect here topic request_snapshot_update:\" + topic); \n    requestEvent.emit('request.snapshot.update.queue',  JSON.parse(message_string));\n  }\n\n\n\n  cb(true);\n//   WebSocket.cb(true);\n//   requestEvent.on(msg.KEYYYYYYY+'.ws', msg.content);\n}\n\n\nfunction closeOnErr(err) {\n  if (!err) return false;\n  winston.error(\"[AMQP] error\", err);\n  amqpConn.close();\n  return true;\n}\n\n// setInterval(function() {\n//     var d = new Date();\n//   publish(exchange, \"request_create\", Buffer.from(\"work work work: \"+d));\n//   publish(exchange, \"request_update\", Buffer.from(\"work2 work work: \"+d));\n// }, 1000);\n\n\nfunction listen() {\n\n    // http://www.squaremobius.net/amqp.node/channel_api.html\n    // https://docs.parseplatform.org/parse-server/guide/#scalability\n\n    requestEvent.on('request.create', function(request) {\n      setImmediate(() => {\n        winston.debug(\"reconnect request.create\")\n        publish(exchange, \"request_create\", Buffer.from(JSON.stringify(request)));\n      });\n    });\n\n    requestEvent.on('request.update', function(request) {\n      setImmediate(() => {\n        winston.debug(\"reconnect request.update\")\n        publish(exchange, \"request_update\", Buffer.from(JSON.stringify(request)));\n      });\n    });\n\n    requestEvent.on('request.participants.join', function(request) {\n      setImmediate(() => {\n        publish(exchange, \"request_participants_join\", Buffer.from(JSON.stringify(request)));\n        winston.debug(\"reconnect participants.join published\")\n      });\n    });\n\n    requestEvent.on('request.participants.leave', function(request) {\n      setImmediate(() => {\n        publish(exchange, \"request_participants_leave\", Buffer.from(JSON.stringify(request)));\n        winston.debug(\"reconnect participants.leave published\")\n      });\n    });\n\n    requestEvent.on('request.participants.update', function(request) {\n      setImmediate(() => {\n        publish(exchange, \"request_participants_update\", Buffer.from(JSON.stringify(request)));\n        winston.debug(\"reconnect participants.update published\")\n      });\n    });\n\n    requestEvent.on('request.update.preflight', function(request) {\n      setImmediate(() => {\n        // winston.info(\"reconnect request.update.preflight\")\n        publish(exchange, \"request_update_preflight\", Buffer.from(JSON.stringify(request)));\n        winston.debug(\"reconnect request.update.preflight published\")\n      });\n    });\n\n    // winston.debug(\"sub to reconnect request.close\");\n    requestEvent.on('request.close', function(request) {\n      setImmediate(() => {\n        winston.debug(\"reconnect request.close\");\n        publish(exchange, \"request_close\", Buffer.from(JSON.stringify(request)));\n      });\n    });\n\n    requestEvent.on('request.close.extended', function(request) {\n      setImmediate(() => {\n        publish(exchange, \"request_close_extended\", Buffer.from(JSON.stringify(request)));\n      });\n    });\n\n    requestEvent.on('request.workingStatus.update', function(request) {\n      setImmediate(() => {\n        publish(exchange, \"request_workingStatus_update\", Buffer.from(JSON.stringify(request)));\n      });\n    });\n\n    requestEvent.on('request.snapshot.update', function(data) {\n      setImmediate(() => {\n        winston.debug(\"reconnect request.snapshot.update\")\n        publish(exchange, \"request_snapshot_update\", Buffer.from(JSON.stringify(data)));\n      });\n    });\n\n\n    messageEvent.on('message.create', function(message) {\n      setImmediate(() => {\n        publish(exchange, \"message_create\", Buffer.from(JSON.stringify(message)));\n      });\n    });\n\n    authEvent.on('project_user.update',function(data) {\n      setImmediate(() => {\n        let user = undefined;\n        let body = undefined;\n        if (data.req ) {\n          if (data.req.user) { //i think is null from chat21webhook \n            user = data.req.user;\n          }\n          if (data.req.body) { \n            body = data.req.body;\n          }\n        }\n        var dat = {updatedProject_userPopulated: data.updatedProject_userPopulated, req: {user: user, body: body}}; //remove request\n        winston.debug(\"dat\",dat);\n        publish(exchange, \"project_user_update\", Buffer.from(JSON.stringify(dat)));\n      });\n    });\n\n\n    botEvent.on('faqbot.update', function(bot) {\n      setImmediate(() => {\n        winston.debug(\"reconnect faqbot.update\")\n        publish(exchange, \"faqbot_update\", Buffer.from(JSON.stringify(bot)));\n        winston.debug(\"reconnect: \"+ Buffer.from(JSON.stringify(bot)))\n      });\n    });\n\n\n    leadEvent.on('lead.create', function(lead) {\n      setImmediate(() => {\n        winston.debug(\"reconnect lead.create\")\n        publish(exchange, \"lead_create\", Buffer.from(JSON.stringify(lead)));\n        winston.debug(\"reconnect: \"+ Buffer.from(JSON.stringify(lead)))\n      });\n    });\n\n\n    leadEvent.on('lead.update', function(lead) {\n      setImmediate(() => {\n        winston.debug(\"reconnect lead.update\")\n        publish(exchange, \"lead_update\", Buffer.from(JSON.stringify(lead)));\n        winston.debug(\"reconnect: \"+ Buffer.from(JSON.stringify(lead)))\n      });\n    });\n\n\n    leadEvent.on('lead.fullname.email.update', function(lead) {\n      setImmediate(() => {\n        winston.debug(\"reconnect lead.fullname.email.update\")\n        publish(exchange, \"lead_fullname_email_update\", Buffer.from(JSON.stringify(lead)));\n        winston.debug(\"reconnect: \"+ Buffer.from(JSON.stringify(lead)))\n      });\n    });\n\n\n    \n\n}\n\nif (process.env.QUEUE_ENABLED === \"true\") {\n    requestEvent.queueEnabled = true;\n    messageEvent.queueEnabled = true;\n    authEvent.queueEnabled = true;\n    botEvent.queueEnabled = true;\n    leadEvent.queueEnabled = true;\n    listen();\n    start();\n    winston.info(\"Queue enabled. endpoint: \" + url );\n} \n\n"
  },
  {
    "path": "pubmodules/queue/reconnectFanout.js",
    "content": "var amqp = require('amqplib/callback_api');\nvar winston = require('../../config/winston');\nconst requestEvent = require('../../event/requestEvent');\nconst messageEvent = require('../../event/messageEvent');\nconst botEvent = require('../../event/botEvent');\nconst authEvent = require('../../event/authEvent');\n// https://elements.heroku.com/addons/cloudamqp\n// https://gist.github.com/carlhoerberg/006b01ac17a0a94859ba#file-reconnect-js\n// http://www.rabbitmq.com/tutorials/tutorial-one-javascript.html\n\n// if the connection is closed or fails to be established at all, we will reconnect\nvar amqpConn = null;\nvar url = process.env.CLOUDAMQP_URL + \"?heartbeat=60\" || \"amqp://localhost\";\n// attento devi aggiornare configMap di PRE E PROD\n// var url = process.env.AMQP_URL + \"?heartbeat=60\" || \"amqp://localhost\";\n\n// MOD0\nvar exchange = 'ws';\n\nfunction start() {\n  amqp.connect(url, function(err, conn) {\n    if (err) {\n      winston.error(\"[AMQP Fanout]\", err);\n      return setTimeout(start, 1000);\n    }\n    conn.on(\"error\", function(err) {\n      if (err.message !== \"Connection closing\") {\n        winston.error(\"[AMQP Fanout] conn error\", err);\n      }\n    });\n    conn.on(\"close\", function() {\n      winston.error(\"[AMQP Fanout] reconnecting\");\n      return setTimeout(start, 1000);\n    });\n\n    winston.info(\"[AMQP Fanout] connected\");\n    amqpConn = conn;\n\n    whenConnected();\n  });\n}\n\nfunction whenConnected() {\n  startPublisher();\n  startWorker();\n}\n\nvar pubChannel = null;\nvar offlinePubQueue = [];\nfunction startPublisher() {\n  amqpConn.createConfirmChannel(function(err, ch) {\n    if (closeOnErr(err)) return;\n    ch.on(\"error\", function(err) {\n      winston.error(\"[AMQP Fanout] channel error\", err);\n    });\n    ch.on(\"close\", function() {\n      winston.info(\"[AMQP Fanout] channel closed\");\n    });\n\n    pubChannel = ch;\n    while (true) {\n      var m = offlinePubQueue.shift();\n      if (!m) break;\n      publish(m[0], m[1], m[2]);\n    }\n  });\n}\n\n// method to publish a message, will queue messages internally if the connection is down and resend later\nfunction publish(exchange, routingKey, content) {\n  try {\n\n    // MOD2 \n    // pubChannel.publish('logs', '', Buffer.from('Hello World!'));\n    pubChannel.publish(exchange, routingKey, content, { },\n    // pubChannel.publish(exchange, routingKey, content, { persistent: true },\n                       function(err, ok) {\n                         if (err) {\n                          winston.error(\"[AMQP Fanout] publish\", err);\n                           offlinePubQueue.push([exchange, routingKey, content]);\n                           pubChannel.connection.close();\n                         }\n                       });\n  } catch (e) {\n    winston.error(\"[AMQP Fanout] publish\", e);\n    offlinePubQueue.push([exchange, routingKey, content]);\n  }\n}\n\n// A worker that acks messages only if processed succesfully\n// var channel;\nfunction startWorker() {\n    amqpConn.createChannel(function(err, ch) {\n      if (closeOnErr(err)) return;\n      ch.on(\"error\", function(err) {\n        winston.error(\"[AMQP Fanout] channel error\", err);\n      });\n      ch.on(\"close\", function() {\n        winston.info(\"[AMQP Fanout] channel closed\");\n      });\n      ch.prefetch(10);//leggila da env\n\n      // ch.assertExchange(exchange, 'topic', {\n      //   durable: true\n      // });\n\n      // MOD1\n      ch.assertExchange(exchange, 'fanout', {durable: false});\n\n      //MOD3 \n      ch.assertQueue('', {exclusive: true}, function(err, _ok) {\n      // ch.assertQueue(\"jobs\", { durable: true }, function(err, _ok) {\n\n        if (closeOnErr(err)) return;\n        \n      //MOD4 \n        ch.bindQueue(_ok.queue, exchange, '', {}, function(err3, oka) {\n          winston.info(\"Queue Fanout bind: \"+_ok.queue+ \" err: \"+err3);\n          winston.info(\"Data Queue Fanout\", oka)\n        });\n\n        // ch.bindQueue(_ok.queue, exchange, \"request_create\", {}, function(err3, oka) {\n        //     console.log(\"queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_create\");\n        //     console.log(\"data queue\", oka)\n        // });\n        // ch.bindQueue(_ok.queue, exchange, \"request_update\", {}, function(err3, oka) {\n        //     console.log(\"queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: request_update\");\n        //     console.log(\"data queue\", oka)\n        // });\n        // ch.bindQueue(_ok.queue, exchange, \"message_create\", {}, function(err3, oka) {\n        //       console.log(\"queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: message_create\");\n        //       console.log(\"data queue\", oka)\n        // });\n        // ch.bindQueue(_ok.queue, exchange, \"project_user_update\", {}, function(err3, oka) {\n        //   console.log(\"queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: project_user_update\");\n        //   console.log(\"data queue\", oka)\n        // });\n        ch.consume(_ok.queue, processMsg, { noAck: false });\n        winston.info(\"Worker Fanout is started\");\n      });\n  \n\n    function processMsg(msg) {\n      work(msg, function(ok) {\n        try {\n          if (ok)\n            ch.ack(msg);\n          else\n            ch.reject(msg, true);\n        } catch (e) {\n          closeOnErr(e);\n        }\n      });\n    }\n  });\n}\n\nfunction work(msg, cb) {\n  const message_string = msg.content.toString();\n  const topic = msg.fields.routingKey //.replace(/[.]/g, '/');\n\n  winston.debug(\"Got Fanout msg topic:\" + topic);\n  \n  winston.debug(\"Got Fanout msg:\"+ message_string +  \" topic:\" + topic);\n\n  if (topic === 'request_create') {\n    winston.debug(\"reconnectfanout here topic:\" + topic);\n    winston.debug(\"reconnect request.update\")\n    requestEvent.emit('request.create.queue.pubsub', JSON.parse(message_string));\n  }\n  if (topic === 'request_update') {\n    winston.debug(\"reconnectfanout here topic:\" + topic);\n    requestEvent.emit('request.update.queue.pubsub',  JSON.parse(message_string));\n  }\n  if (topic === 'message_create') {\n    winston.debug(\"reconnectfanout here topic:\" + topic);\n    messageEvent.emit('message.create.queue.pubsub', JSON.parse(message_string));\n  }\n  if (topic === 'project_user_update') {\n    winston.debug(\"reconnectfanout here topic:\" + topic);\n    authEvent.emit('project_user.update.queue.pubsub', JSON.parse(message_string));\n  }\n  if (topic === 'faqbot_update') {\n    winston.debug(\"reconnectfanout here topic faqbot_update:\" + topic);\n    botEvent.emit('faqbot.update.queue.pubsub', JSON.parse(message_string));\n  }\n  if (topic === 'request_snapshot_add') {\n    winston.debug(\"reconnectfanout here topic request_snapshot_add:\" + topic); \n    requestEvent.emit('request.snapshot.add.queue.pubsub', JSON.parse(message_string));\n  }\n  cb(true);\n//   WebSocket.cb(true);\n//   requestEvent.on(msg.KEYYYYYYY+'.ws', msg.content);\n}\n\n\nfunction closeOnErr(err) {\n  if (!err) return false;\n  winston.error(\"[AMQP Fanout] error\", err);\n  amqpConn.close();\n  return true;\n}\n\n// setInterval(function() {\n//     var d = new Date();\n//   publish(exchange, \"request_create\", Buffer.from(\"work work work: \"+d));\n// }, 10000);\n\n\n\n// http://www.squaremobius.net/amqp.node/channel_api.html\n// https://docs.parseplatform.org/parse-server/guide/#scalability\n\n\n\nfunction listen() {\n    \n  requestEvent.on('request.create', function(request) {\n    setImmediate(() => {\n      publish(exchange, \"request_create\", Buffer.from(JSON.stringify(request)));\n    });\n  });\n\n  requestEvent.on('request.update', function(request) {\n    setImmediate(() => {\n      publish(exchange, \"request_update\", Buffer.from(JSON.stringify(request)));\n    });\n  });\n\n\n  messageEvent.on('message.create', function(message) {\n    setImmediate(() => {\n      publish(exchange, \"message_create\", Buffer.from(JSON.stringify(message)));\n    });\n  });\n  \n  authEvent.on('project_user.update',function(data) {\n    setImmediate(() => {\n      let user = undefined;\n      let body = undefined;\n        if (data.req ) {\n          if (data.req.user) { //i think is null from chat21webhook \n            user = data.req.user;\n          }\n          if (data.req.body) {\n            body = data.req.body;\n          }\n        }\n        var dat = {updatedProject_userPopulated: data.updatedProject_userPopulated, req: {user: user, body: body}}; //remove request\n        winston.debug(\"dat\",dat);\n\n      publish(exchange, \"project_user_update\", Buffer.from(JSON.stringify(dat)));\n    });\n  });\n\n\n  botEvent.on('faqbot.update', function(bot) {\n    setImmediate(() => {\n      winston.debug(\"reconnect faqbot.update\")\n      publish(exchange, \"faqbot_update\", Buffer.from(JSON.stringify(bot)));\n      winston.debug(\"reconnect fan: \"+ Buffer.from(JSON.stringify(bot)))\n    });\n  });\n\n\n}\n\nif (process.env.QUEUE_ENABLED === \"true\") {\n    requestEvent.queueEnabled = true;\n    messageEvent.queueEnabled = true;\n    authEvent.queueEnabled = true; \n    botEvent.queueEnabled = true;\n    listen();\n    start();\n    winston.info(\"Queue Fanout enabled. endpoint: \" + url );\n} \n\n"
  },
  {
    "path": "pubmodules/rasa/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst rasa = require(\"@tiledesk/tiledesk-rasa-connector\");\nconst rasaRoute = rasa.router;\n\n\n\n\n\nmodule.exports = { listener: listener, rasaRoute: rasaRoute };"
  },
  {
    "path": "pubmodules/rasa/listener.js",
    "content": "const botEvent = require('../../event/botEvent');\nvar Faq_kb = require(\"../../models/faq_kb\");\nvar winston = require('../../config/winston');\nconst rasa = require(\"@tiledesk/tiledesk-rasa-connector\");\nvar configGlobal = require('../../config/global');\n\nvar port = process.env.PORT || '3000';\n\nconst BOT_RASA_ENDPOINT = process.env.BOT_RASA_ENDPOINT || \"http://localhost:\" + port+ \"/modules/rasa/rasabot\";\nwinston.debug(\"BOT_RASA_ENDPOINT: \" + BOT_RASA_ENDPOINT);\n\n// if (BOT_RASA_ENDPOINT) {\n  winston.info(\"Rasa endpoint: \" + BOT_RASA_ENDPOINT);\n// } else {\n//    winston.info(\"Rasa endpoint not configured\");\n// }\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('Rasa apiUrl: '+ apiUrl);\n\nclass Listener {\n\n    listen(config) {\n\n        winston.info('Rasa Listener listen');\n        winston.debug(\"rasa config databaseUri: \" + config.databaseUri);  \n        \n\n        var that = this;\n\n\n        rasa.startRasa(\n            {\n                KVBASE_COLLECTION : process.env.KVBASE_COLLECTION,\n                MONGODB_URI: config.databaseUri,          \n                API_ENDPOINT: apiUrl,   \n                log: process.env.RASABOT_LOG\n            }, () => {\n                winston.info(\"RASA proxy server successfully started.\");   \n            });\n\n\n      \n        botEvent.on('faqbot.create', function(bot) {\n            if (BOT_RASA_ENDPOINT) {\n\n                winston.debug('bot.type:'+bot.type); \n                if (bot.type===\"rasa\") {\n\n                    winston.debug('qui.type:'+bot.type); \n\n\n                    Faq_kb.findByIdAndUpdate(bot.id, {\"url\":BOT_RASA_ENDPOINT}, { new: true, upsert: true }, function (err, savedFaq_kb) {\n\n                    // bot.save(function (err, savedFaq_kb) {\n                        if (err) {\n                         return winston.error('error saving faqkb rasa ', err)\n                        }\n                        botEvent.emit(\"faqbot.update\",savedFaq_kb); //cache invalidation\n                        winston.verbose('Saved faqkb rasa', savedFaq_kb.toObject())      \n                    });\n                }\n            }\n        });\n        \n    }\n\n}\n\nvar listener = new Listener();\n\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/routing-queue/index.js",
    "content": "const listener = require(\"./listener\");\nconst listenerQueued = require(\"./listenerQueued\");\n\n\nmodule.exports = {listener:listener, listenerQueued: listenerQueued };"
  },
  {
    "path": "pubmodules/routing-queue/listener.js",
    "content": "const departmentEvent = require('../../event/departmentEvent');\nvar Request = require('../../models/request');\nvar winston = require('../../config/winston');\n\n\n// var request = require('retry-request', {\n//     request: require('request')\n//   });\n\n// TODO riabilitare questo\n\n// const ROUTE_QUEUE_ENDPOINT = process.env.ROUTE_QUEUE_ENDPOINT;\n// winston.debug(\"ROUTE_QUEUE_ENDPOINT: \" + ROUTE_QUEUE_ENDPOINT);\n\n// if (ROUTE_QUEUE_ENDPOINT) {\n//   winston.info(\"Route queue endpoint: \" + ROUTE_QUEUE_ENDPOINT);\n// } else {\n//    winston.info(\"Route queue endpoint not configured\");\n// }\n\n\nclass Listener {\n\n \n  constructor() {\n    this.enabled = true;\n    if (process.env.ROUTE_QUEUE_ENABLED==\"false\" || process.env.ROUTE_QUEUE_ENABLED==false) {\n        this.enabled = false;\n    }\n    winston.debug(\"Listener this.enabled: \"+ this.enabled);\n}\n\n     nextOperator(array, index) {\n        // console.log('array: ', array);\n        // console.log('index: ' + index);\n    \n        index = index || 0;\n    \n        if (array === undefined || array === null)\n          array = [];\n        else if (!Array.isArray(array))\n          throw new Error('Expecting argument to RoundRound to be an Array');\n    \n        // return function () {\n            index++;\n          if (index >= array.length) index = 0;\n          // console.log('index: ' + index);\n          return array[index];\n        // };\n      }\n\n\n    listen() {\n\n      if (this.enabled==true) {\n        winston.info(\"Route queue Listener listen\");\n      } else {\n          return winston.info(\"Route queue Listener disabled\");\n      }\n\n        var that = this;\n\n        departmentEvent.on('operator.select.base2', async (res) => {\n            // departmentEvent.prependListener('operator.select', async (data) => {\n            \n              var operatorsResult = res.result;\n              winston.debug('operator.select.base2 res', res);   \n          \n          \n              var disableWebHookCall = res.disableWebHookCall;\n              winston.debug(\"operator.select.base2 disableWebHookCall: \"+ disableWebHookCall);\n          \n            if (disableWebHookCall===true) {      \n              winston.debug(\"operator.select.base2 disableWebHookCall enabled: \"+ disableWebHookCall);\n              // return callNextEvent('operator.select', res);\n              return res.resolve(operatorsResult);\n          \n            }\n          \n        \n          \n          \n          \n          \n            var project = operatorsResult.project;\n            var max_agent_assigned_chat = undefined;\n          \n            \n            // console.log(\"project: \", project);\n          \n            if (project && project.settings && project.settings.chat_limit_on && project.settings.max_agent_assigned_chat) {\n              max_agent_assigned_chat = project.settings.max_agent_assigned_chat;\n              winston.debug('operator.select max_agent_assigned_chat: '+max_agent_assigned_chat);   \n          \n            } else {\n              winston.debug(\"chat_limit_on not defined calling next \");\n              return departmentEvent.callNextEvent('operator.select', res);\n            }\n          \n          \n             \n          \n            // winston.info('qui', operatorsResult.available_agents.length);   \n            operatorsResult.available_agents_request  = [];\n          \n            if (operatorsResult && operatorsResult.available_agents && operatorsResult.available_agents.length > 0) {        \n          \n              // winston.info('qui1');   \n               //  qui1000\n              var query = {id_project: operatorsResult.id_project, status: {$lt:1000}};      \n              // asyncForEach(operatorsResult.available_agents, async (aa) => {\n              for (const aa of operatorsResult.available_agents) {\n                let user_id;\n                if (aa.id_user._id) {\n                  user_id = aa.id_user._id.toString();// attento qui\n                } else {\n                  user_id = aa.id_user;// attento qui\n                }\n                query.participants = user_id;\n                winston.debug(\"department operators query:\" , query);\n\n\n                // questa cosa nn va bene. nn puoi usare ? number_assigned_requests??\n                var count =  await Request.countDocuments(query);\n                winston.debug(\"department operators count: \"+ count);\n                operatorsResult.available_agents_request.push({project_user: aa, openRequetsCount : count});\n          \n              }\n          \n            }\n          \n          \n\n         \n             \n          \n              var available_agents_request = operatorsResult.available_agents_request;\n              var available_agents_not_busy = [];\n              if (available_agents_request && available_agents_request.length>0) {\n                for (const aa of available_agents_request) {\n                // console.log(\"aa.openRequetsCount \", aa.openRequetsCount, \" for:\", aa.project_user.id_user);\n          \n                  var maxAssignedchatForUser = max_agent_assigned_chat;\n          \n                  var max_agent_assigned_chat_specific_user = aa.project_user.max_assigned_chat;\n                  // console.log(\"max_agent_assigned_chat_specific_user: \", max_agent_assigned_chat_specific_user);\n          \n                  if (max_agent_assigned_chat_specific_user && max_agent_assigned_chat_specific_user!=-1 ) {\n                    maxAssignedchatForUser = max_agent_assigned_chat_specific_user;           \n                  }            \n                  \n                  winston.debug(\"maxAssignedchatForUser: \"+ maxAssignedchatForUser);\n          \n                  if (aa.openRequetsCount < maxAssignedchatForUser) {\n                    winston.debug(\"adding  \"+ aa.project_user.id_user+ \" with openRequetsCount: \"+ aa.openRequetsCount +\" and with maxAssignedchatForUser \" + maxAssignedchatForUser +\" to available_agents_not_busy\" );\n                    available_agents_not_busy.push(aa.project_user);\n                  }\n                }\n              } else {\n                winston.debug(\"available_agents_request not defined\");\n              }\n          \n              winston.debug(\"available_agents_not_busy\", available_agents_not_busy);\n          \n          \n\n                 // TODO non riassegnare allo stesso utente usa history oppure lastabandonedby in attributes \n            // in project_user sengni il numero di abandoni se uguale a psettng lo metto offline\n            winston.debug(\"res.context\",res.context);  \n\n            winston.debug(\"res.context.request.attributes\",res.context.request.attributes);  \n\n              if (res.context && res.context.request  && res.context.request && \n                res.context.request.attributes && res.context.request.attributes.abandoned_by_project_users ) {  //&& res.context.request.attributes.queue_important==false  (vip sla continuo reassign)\n                  \n                  // var abandoned_by_project_users= {\"5ecd44cfa3f5670034109b44\":true,\"5ecd56a10e7d2d00343203cc\":true}\n\n                  winston.debug(\"res.context.request.attributes.abandoned_by_project_users: \", res.context.request.attributes.abandoned_by_project_users );\n                  \n                  var abandoned_by_project_usersAsArray = Object.keys(res.context.request.attributes.abandoned_by_project_users);\n\n                  if (abandoned_by_project_usersAsArray.length>0 )  {\n                    winston.debug(\"abandoned_by_project_usersAsArray\", abandoned_by_project_usersAsArray);\n\n                    var available_agents_not_busy = available_agents_not_busy.filter(projectUser=> !abandoned_by_project_usersAsArray.includes(projectUser._id.toString()))\n                    \n                    if (available_agents_not_busy.length == 0) {\n                      res.context.request.attributes.fully_abandoned = true;\n                    }\n\n                    winston.debug(\"available_agents_not_busy after: \", available_agents_not_busy );                           \n                  }\n              }\n\n              // if (res.context && res.context.request  && res.context.request && \n              //   res.context.request.attributes && res.context.request.attributes.last_abandoned_by )  {\n              //     winston.debug(\"res.context.request.attributes.last_abandoned_by: \"+res.context.request.attributes.last_abandoned_by );\n              //     let last_abandoned_by_index = available_agents_not_busy.findIndex(projectUser => projectUser._id.toString() === res.context.request.attributes.last_abandoned_by);\n              //     winston.debug(\"last_abandoned_by_index: \"+last_abandoned_by_index );\n              //     if (last_abandoned_by_index>-1) {\n              //       available_agents_not_busy.splice(last_abandoned_by_index, 1);\n              //       winston.debug(\"available_agents_not_busy after\", available_agents_not_busy );\n              //     }                  \n\n              // }\n\n\n              var lastOperatorId  = operatorsResult.lastOperatorId;\n          \n              let lastOperatorIndex = available_agents_not_busy.findIndex(projectUser => projectUser.id_user.toString() === lastOperatorId);\n              winston.debug(\"lastOperatorIndex: \"+ lastOperatorIndex);\n              var nextOper = that.nextOperator(available_agents_not_busy, lastOperatorIndex);\n              // console.log(\"nextOper: \", nextOper);\n              var nextOperatorId = undefined; \n              if (nextOper && nextOper.id_user) {\n                nextOperatorId = nextOper.id_user;\n                winston.verbose(\"nextOperatorId: \"+ nextOperatorId);\n          \n                operatorsResult.operators = [{id_user: nextOperatorId}];\n          \n                // return resolve(result);\n              } else {\n                winston.debug(\"nextOper is not defined\");\n                operatorsResult.operators = [];\n                // return resolve(result);\n              }\n          \n          \n              return departmentEvent.callNextEvent('operator.select', res);\n          \n           \n          \n          });\n\n       \n    }\n\n}\n\nvar listener = new Listener();\n\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/routing-queue/listenerQueued.js",
    "content": "const authEvent = require('../../event/authEvent');\nconst requestEvent = require('../../event/requestEvent');\nvar Project = require('../../models/project');\nvar Project_user = require('../../models/project_user');\nvar Request = require('../../models/request')\nvar winston = require('../../config/winston');\n\nvar ProjectUserUtil = require(\"../../utils/project_userUtil\");\n\n// var request = require('retry-request', {\n//     request: require('request')\n//   });\n\n// TODO riabilitare questo\n\n// const ROUTE_QUEUE_ENDPOINT = process.env.ROUTE_QUEUE_ENDPOINT;\n// winston.debug(\"ROUTE_QUEUE_ENDPOINT: \" + ROUTE_QUEUE_ENDPOINT);\n\n// if (ROUTE_QUEUE_ENDPOINT) {\n//   winston.info(\"Route queue endpoint: \" + ROUTE_QUEUE_ENDPOINT);\n// } else {\n//    winston.info(\"Route queue endpoint not configured\");\n// }\n\n\n\n\n// TODO web socket is not supported with queue\nclass Listener {\n\n \n  constructor() {\n    this.enabled = true;\n    if (process.env.ROUTE_QUEUE_ENABLED==\"false\" || process.env.ROUTE_QUEUE_ENABLED==false) {\n        this.enabled = false;\n    }\n    winston.debug(\"Listener this.enabled: \"+ this.enabled);\n  }\n\n \n  // db.getCollection('project_users').find({\"number_assigned_requests\" : {\"$lt\":0}}).count()\n\n  // New version of updateProjectUser() method.\n  // This will not increment or decrement the number_assigned_requests field but search the exact number of assigned conversation to the project user\n  updateProjectUser(id_user, id_project, operation) {\n    // winston.debug(\"Route queue updateProjectUser start operation: \" + operation + \"id_user \" + id_user + \" id_project \" + id_project);\n    \n    // escludi id_user che iniziano con bot_\n    if (id_user.startsWith('bot_')) {\n      return winston.warn(\"Chatbot is not a project_user. Skip update.\")\n    }\n\n    return Request.countDocuments({ id_project: id_project, participantsAgents: id_user, status: { $lt: 1000 }, draft: { $in: [null, false] }, workingStatus: { $ne: 'pending' } }, (err, requestsCount) => {\n      winston.verbose(\"requestsCount for id_user: \", id_user, \"and project: \", id_project, \"-->\", requestsCount);\n      if (err) {\n        return winston.error(err);\n      }\n\n      return Project_user\n        .findOneAndUpdate({ id_user: id_user, id_project: id_project }, { number_assigned_requests: requestsCount }, { new: true, upsert: false }, function (err, updatedPU) {\n          if (err) {\n            return winston.error(err);\n          }\n          // winston.debug(\"Route queue number_assigned_requests +1 :\" + updatedPU.id);\n          // winston.debug(\"Route queue number_assigned_requests +1 :\" + updatedPU.id);\n          winston.debug(\"Route queue number_assigned_requests updated to \" + requestsCount + \"for project user \" + updatedPU.id);\n  \n          updatedPU.populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1 } }, function (err, updatedProject_userPopulated) {\n  \n            var pu = updatedProject_userPopulated.toJSON();\n  \n            return Project.findById(id_project).exec(function (err, project) {\n              pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, project.settings && project.settings.max_agent_assigned_chat);\n              winston.debug(\"Route queue pu.isBusy: \" + pu.isBusy);\n  \n              authEvent.emit('project_user.update', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true }); //if queued with jobs -> websocket notification on project_user.update doesn't work??? forse si in quanto viene convertito in .pub.queue e poi rifunziiona\n  \n  \n              // project_user.update triggers activityArchiver(tested), cache invalidation(tested), subscriptionNotifierQueued and websocket(tested works from queue i trigger ws)\n              if (requestEvent.queueEnabled) {  //force to .queue to be catched into the queue (activity archiver, subscriptionNotifierQueued )\n                authEvent.emit('project_user.update.queue', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true });\n              }\n  \n            })\n  \n          });\n  \n        });\n    })\n\n  }\n\n  _updateProjectUser(id_user, id_project, operation) {\n    winston.debug(\"Route queue updateProjectUser start operation: \" + operation + \"id_user \" + id_user + \" id_project \" + id_project);\n    return Project_user\n      .findOneAndUpdate({ id_user: id_user, id_project: id_project }, { $inc: { 'number_assigned_requests': operation } }, { new: true, upsert: false }, function (err, updatedPU) {\n        if (err) {\n          return winston.error(err);\n        }\n        winston.debug(\"Route queue number_assigned_requests +1 :\" + updatedPU.id);\n        winston.debug(\"Route queue number_assigned_requests +1 :\" + updatedPU.id);\n\n        updatedPU.populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1 } }, function (err, updatedProject_userPopulated) {\n\n          var pu = updatedProject_userPopulated.toJSON();\n\n          return Project.findById(id_project).exec(function (err, project) {\n            pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, project.settings && project.settings.max_agent_assigned_chat);\n            winston.debug(\"Route queue pu.isBusy: \" + pu.isBusy);\n\n            authEvent.emit('project_user.update', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true }); //if queued with jobs -> websocket notification on project_user.update doesn't work??? forse si in quanto viene convertito in .pub.queue e poi rifunziiona\n\n\n            // project_user.update triggers activityArchiver(tested), cache invalidation(tested), subscriptionNotifierQueued and websocket(tested works from queue i trigger ws)\n            if (requestEvent.queueEnabled) {  //force to .queue to be catched into the queue (activity archiver, subscriptionNotifierQueued )\n              authEvent.emit('project_user.update.queue', { updatedProject_userPopulated: pu, req: undefined, skipArchive: true });\n            }\n\n          })\n\n        });\n\n      });\n  }\n\n    updateParticipatingProjectUsers(request, operation) {\n        winston.debug(\"Route queue request.participatingAgents\", request.participatingAgents);\n        if (request.participatingAgents.length>0) {\n            request.participatingAgents.forEach(user => {\n              winston.debug(\"request.participatingAgents user\",user); //it is a user and not a project_user\n                var userid = user.id || user._id;\n                winston.debug(\"updateParticipatingProjectUsers userid: \"+userid); \n\n                this.updateProjectUser(userid, request.id_project, operation);                \n            });\n        } \n      }  \n\n    listen() {\n\n      if (this.enabled==true) {\n        winston.info(\"Route queue with queue Listener listen\");\n      } else {\n          return winston.info(\"Route queue with queue Listener disabled\");\n      }\n\n        var that = this;\n \n        // TODO fai versione che passa anche project\n        var requestCreateKey = 'request.create';\n        if (requestEvent.queueEnabled) {\n          requestCreateKey = 'request.create.queue';\n        }\n        winston.debug('Route queue requestCreateKey: ' + requestCreateKey);\n   \n        requestEvent.on(requestCreateKey, async (request) => {\n            setImmediate(() => {\n              winston.debug('Route queue requestCreate');\n              this.updateParticipatingProjectUsers(request, +1);  \n            });\n        });\n\n          // TODO usa versione complete con project per evitare query??\n        var requestCloseKey = 'request.close';   //request.close event here queued under job\n        if (requestEvent.queueEnabled) {\n          requestCloseKey = 'request.close.queue';\n        }\n        winston.debug('Route queue requestCloseKey: ' + requestCloseKey);\n\n        requestEvent.on(requestCloseKey, async (request) => {    //request.close event here noqueued\n          winston.debug(\"request.close event here 4\")\n          setImmediate(() => {\n            winston.debug('Route queue requestClose');\n            this.updateParticipatingProjectUsers(request, -1);          \n          });\n        });\n\n\n        var requestParticipantsJoinKey = 'request.participants.join';\n        if (requestEvent.queueEnabled) {\n          requestParticipantsJoinKey = 'request.participants.join.queue';\n        }\n        winston.debug('Route queue  requestParticipantsJoinKey: ' + requestParticipantsJoinKey);\n   \n        requestEvent.on(requestParticipantsJoinKey, async (data) => {\n          winston.debug('Route queue ParticipantsJoin');\n\n          var request = data.request;\n          var member = data.member;\n          setImmediate(() => {\n            this.updateProjectUser(member, request.id_project, 1);          \n          });\n        });\n\n        var requestParticipantsLeaveKey = 'request.participants.leave';\n        if (requestEvent.queueEnabled) {\n          requestParticipantsLeaveKey = 'request.participants.leave.queue';\n        }\n        winston.debug('Route queue  requestParticipantsLeaveKey: ' + requestParticipantsLeaveKey);\n   \n        requestEvent.on(requestParticipantsLeaveKey, async (data) => {\n          winston.debug('Route queue ParticipantsLeave');\n\n          var request = data.request;\n          var member = data.member;\n          setImmediate(() => {\n            this.updateProjectUser(member, request.id_project, -1);          \n          });\n        });\n\n        var requestParticipantsUpdateKey = 'request.participants.update';\n        if (requestEvent.queueEnabled) {\n         requestParticipantsUpdateKey = 'request.participants.update.queue';\n        }\n        winston.debug('Route queue  requestParticipantsUpdateKey: ' + requestParticipantsUpdateKey);\n   \n        requestEvent.on(requestParticipantsUpdateKey, async (data) => {\n          winston.debug('Route queue Participants Update');\n\n          var request = data.request;\n          var removedParticipants = data.removedParticipants;\n          var addedParticipants = data.addedParticipants;\n\n          setImmediate(() => {\n\n            addedParticipants.forEach(participant => {\n              winston.debug('addedParticipants participant', participant);\n              this.updateProjectUser(participant, request.id_project, 1);          \n            });\n\n            removedParticipants.forEach(participant => {\n              winston.debug('removedParticipants participant', participant);\n              this.updateProjectUser(participant, request.id_project, -1);          \n            });\n\n          });\n        });\n\n        var requestWorkingStatusUpdateKey = 'request.workingStatus.update';\n        if (requestEvent.queueEnabled) {\n          requestWorkingStatusUpdateKey = 'request.workingStatus.update.queue';\n        }\n        winston.debug('Route queue requestWorkingStatusUpdateKey: ' + requestWorkingStatusUpdateKey);\n\n        requestEvent.on(requestWorkingStatusUpdateKey, async (data) => {\n          winston.debug('Route queue WorkingStatus Update');\n\n          var request = data.request;\n          var participantIds = (request.participantsAgents && request.participantsAgents.length)\n            ? request.participantsAgents\n            : (request.participatingAgents || []).map(u => u._id || u.id);\n          setImmediate(() => {\n            participantIds.forEach(id_user => {\n              if (id_user && !String(id_user).startsWith('bot_')) {\n                this.updateProjectUser(id_user, request.id_project, 0);\n              }\n            });\n          });\n        });\n    }\n\n}\n\nvar listener = new Listener();\n\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/rules/appRules.js",
    "content": "const conciergeBot = require('./conciergeBot');\n\nvar winston = require('../../config/winston');\n\nclass AppRules {\n\n    start() {\n    \n        //DEPRECATED\n        // roundRobinOperator.start();\n           conciergeBot.listen();\n    }\n}\n\nvar appRules = new AppRules();\nmodule.exports = appRules;"
  },
  {
    "path": "pubmodules/rules/conciergeBot.js",
    "content": "\nconst requestEvent = require('../../event/requestEvent');\nconst messageEvent = require('../../event/messageEvent');\nvar messageService = require('../../services/messageService');\nvar requestService = require('../../services/requestService');\nvar leadService = require('../../services/leadService');\nvar MessageConstants = require(\"../../models/messageConstants\");\nvar LeadConstants = require(\"../../models/leadConstants\");\nvar winston = require('../../config/winston');\nvar i8nUtil = require(\"../../utils/i8nUtil\");\nvar BotFromParticipant = require(\"../../utils/botFromParticipant\");\nconst RequestConstants = require('../../models/requestConstants');\n\nclass ConciergeBot {\n\n\n    listen() {\n\n        var that = this;\n        winston.info(\"ConciergeBot listener start \");\n        \n\n /*\n        requestEvent.on('request.create',  function(request) {   \n            winston.info(\" ConciergeBot request create\", request);\n            if (request.status < 100 && request.department.id_bot) {\n                // addParticipantByRequestId(request_id, id_project, member) {\n                    requestService.addParticipantByRequestId(request.request_id, request.id_project, \"bot_\"+request.department.id_bot).catch((err) => {\n                      winston.error(\"(ConciergeBot) addParticipantByRequestId error\", err);\n                    });\ndevi mandare un messaggio welcome tu altrimenti il bot inserito successivamente al primo messaggio non riceve il welcome iniziale\n            }\n        });\n*/\n        messageEvent.on('message.create',  function(message) {\n            winston.debug(\" ConciergeBot message create\", message);\n            //var botId = BotFromParticipant.getBotFromParticipants(message.request.participants);\n\n            setImmediate(() => {\n\n                // winston.debug(\" ConciergeBot message.request.preflight: \"+message.request.preflight);\n                // winston.debug(\" ConciergeBot message.sender: \"+message.sender);\n                // winston.debug(\" ConciergeBot message.request.lead.lead_id: \"+message.request.lead.lead_id);\n                // winston.debug(\" ConciergeBot message.text: \"+message.text);\n                // winston.debug(\" ConciergeBot message.request.first_text: \"+message.request.first_text);\n\n                // lead_id used. Change it?\n                \n                if (message.request && message.request.preflight === true  && message.sender == message.request.lead.lead_id && message.text != message.request.first_text) {\n                    winston.debug(\"conciergebot: \" + message.request.first_text );\n                // if (message.request && message.request.preflight === true  && message.sender == message.request.lead.lead_id && message.text != message.request.first_text ) {\n                    // if (message.request.status < 100 && message.sender == message.request.lead.lead_id && message.text != message.request.first_text ) {\n                    // if (message.request.status < 100 && message.sender == message.request.lead.lead_id && message.text != message.request.first_text && !botId) {\n                \n                    winston.debug(\"message send from lead with preflight on\");\n\n                        // changeFirstTextAndPreflightByRequestId(request_id, id_project, first_text, preflight) {\n\n                        let first_text =  message.text;\n                        if (message.type === MessageConstants.MESSAGE_TYPE.IMAGE)  {\n                            first_text = \"Image\";\n                            winston.debug(\"setting first_text to image\");\n                        }      \n\n                        if (message.type === MessageConstants.MESSAGE_TYPE.FRAME)  {\n                            first_text = \"Frame\";\n                            winston.debug(\"setting first_text to frame\");\n                        }\n\n\n                        requestService.changeFirstTextAndPreflightByRequestId(message.request.request_id, message.request.id_project, first_text, false).then(function (reqChanged) {\n                        //TESTA QUESTO\n\n                                winston.debug(\"message.request.status: \"+message.request.status);\n                                \n                                winston.debug(\"message.request.hasBot: \"+message.request.hasBot);\n                                // winston.info(\"message.request.department.id_bot: \"+message.request.department.id_bot);\n                                if (message.request.status === 50 &&  message.request.hasBot === false) { \n                                    // if (message.request.status === 50 &&  message.request.department.id_bot == undefined) { \n                                    //apply only if the status is temp and no bot is available. with agent you must reroute to assign temp request to an agent \n                                    winston.debug(\"rerouting\");\n                                    // reroute(request_id, id_project, nobot)\n                                    \n                                    requestService.reroute(message.request.request_id, message.request.id_project, false ).then(function() {\n                                        winston.debug(\"reroute successful from event message.create\");\n                                    }).catch((err) => {\n                                        winston.error(\"ConciergeBot error reroute: \" + err);\n                                    });     \n                                }\n                                \n                                // updateStatusWitId(lead_id, id_project, status)\n                                // lead_id used. Change it?\n                                leadService.updateStatusWitId(message.request.lead.lead_id, message.request.id_project, LeadConstants.NORMAL);\n                            // });\n                        });\n\n\n                        // do not update lead here: otherwise it override contact info of the console\n\n                        // if (message.attributes.userFullname) {\n                        //     var  userFullname = message.attributes.userFullname;\n                        //     winston.info(\"concierge userFullname: \"+ userFullname);\n\n                        //     if (!userFullname) {\n                        //         userFullname = message.sender_fullname;\n                        //     }\n\n                        //     var userEmail = message.attributes.userEmail;\n                        //     winston.info(\"concierge userEmail:\" + userEmail);\n\n                        //     leadService.updateWitId(message.sender, userFullname, userEmail, message.request.id_project).then(function (updatedLead) {\n                        //         winston.info(\"concierge updated lead\", updatedLead);                            \n                        //     });\n                        \n                        // }else {\n                        //     winston.info(\"concierge message.attributes.userFullname null\");\n                        // }\n\n\n\n                        \n                }\n            });       \n\n        });\n        \n\n       \n        \n\n        requestEvent.on('request.participants.join',  function(data) {       \n                    let request = data.request;\n                    let member = data.member;\n\n                setImmediate(() => {\n                    winston.debug(\"ConciergeBot member: \" + member);\n                    // botprefix\n                    if (member.indexOf(\"bot_\")==-1) {\n                        var botId = BotFromParticipant.getBotFromParticipants(request.participants);\n                        if (botId) {\n                            winston.verbose(\"ConciergeBot: removing bot with id: \" + botId + \" from the request with request_id: \" + request.request_id + \" from the project with id: \" + request.id_project);\n                            \n                            // botprefix\n                            // removeParticipantByRequestId(request_id, id_project, member) \n                            requestService.removeParticipantByRequestId(request.request_id, request.id_project,\"bot_\"+botId ).catch((err) => {\n                                winston.error(\"(ConciergeBot) removeParticipantByRequestId error\", err)\n                            });\n                        }      \n                    }\n                    \n\n                });\n            });\n\n            requestEvent.on('request.create',  function(request) {   \n                setImmediate(() => {                  \n                    // that.welcomeOnJoin(request);\n                    // that.welcomeAgentOnJoin(request);\n                });\n            });\n\n            requestEvent.on('request.participants.update',  function(data) {                      \n                let request = data.request;            \n                setImmediate(() => {\n                  \n\n                    // that.welcomeOnJoin(request);\n                    that.welcomeAgentOnJoin(request);\n                       \n                });\n\n            });\n\n\n            requestEvent.on('request.participants.join',  function(data) {       \n                let request = data.request;\n                let member = data.member;\n\n                setImmediate(() => {\n                  \n\n                    // manda solo a nuovo agente\n\n                    // that.welcomeOnJoin(request);\n                    that.welcomeAgentOnJoin(request, member);\n                       \n                });\n\n                \n            });\n\n\n\n\n\n        requestEvent.on('request.close',  function(request) {          \n\n            setImmediate(() => {\n                                      \n                        winston.debug(\"ConciergeBot send close bot message\",request);     \n                                            \n                        // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) \n                            messageService.send(\n                                'system', \n                                'Bot',                                     \n                                request.request_id,\n                                \"Chat closed\", \n                                request.id_project,\n                                'system', \n                                {subtype:\"info/support\", \"updateconversation\" : false, messagelabel: {key: \"CHAT_CLOSED\"}},   // TODO send request.closed_by <- c'è il campo l'ho verificato\n                                undefined,\n                                request.language\n                            );\n\n                           \n                        \n                    \n                \n                });\n\n    });\n\n\n\n\n    requestEvent.on('request.reopen',  function(request) {          \n\n            setImmediate(() => {\n                                        \n                        winston.debug(\"ConciergeBot send reopen bot message\");     \n                                            \n                        // return chatApi.sendGroupMessage(\"system\", \"Bot\", group_id, \"Support Group\", \"Chat closed\", app_id, {subtype:\"info/support\",\"updateconversation\" : false, messagelabel: {key: \"CHAT_CLOSED\"} });\n\n                            messageService.send(\n                                'system', \n                                'Bot',                                     \n                                request.request_id,\n                                \"Chat reopened\", \n                                request.id_project,\n                                'system',                                       //changed from false to true 6Aug22 because reopen from dashboard history doesn't update convs in ionic\n                                {subtype:\"info/support\", \"updateconversation\" : true, messagelabel: {key: \"CHAT_REOPENED\"}},\n                                undefined,\n                                request.language\n\n                            );\n\n                            \n                        \n                    \n                \n                });       \n\n    });\n\n\n\n\n\n    }\n\n\n\n    // unused\n    // welcomeOnJoin(request) {\n    //     var botId = BotFromParticipant.getBotFromParticipants(request.participants);\n    //     if (!botId) {                        \n    //     // if (!request.department.id_bot) {\n            \n    //         winston.debug(\"ConciergeBot send welcome bot message\");     \n          \n    //           // TODO if (request is assigned allora manda we are putting inn touch )\n    //         // controlla dopo reassing\n    //         if (request.status == RequestConstants.ASSIGNED) {\n    //             if (request.participants.length==0) {\n    //                 // if (request.availableAgents.length==0) {\n                       \n    //                     // messageService.send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type);\n    //                     messageService.send(\n    //                         'system', \n    //                         'Bot',                                     \n    //                         request.request_id,\n    //                         i8nUtil.getMessage(\"NO_AVAILABLE_OPERATOR_MESSAGE\", request.language, MessageConstants.LABELS), \n    //                         request.id_project,\n    //                         'system', \n    //                         //{\"updateconversation\" : false, messagelabel: {key: \"NO_AVAILABLE_OPERATOR_MESSAGE\"}}\n    //                         {messagelabel: {key: \"NO_AVAILABLE_OPERATOR_MESSAGE\"}},\n    //                         undefined,\n    //                         request.language\n                           \n\n    //                     );\n                    \n                        \n    //                 }else {\n        \n    //                     messageService.send(\n    //                         'system', \n    //                         'Bot',                                     \n    //                         request.request_id,\n    //                         i8nUtil.getMessage(\"JOIN_OPERATOR_MESSAGE\", request.language, MessageConstants.LABELS), \n    //                         request.id_project,\n    //                         'system', \n    //                         {messagelabel: {key: \"JOIN_OPERATOR_MESSAGE\"}},\n    //                         // {\"updateconversation\" : false, messagelabel: {key: \"JOIN_OPERATOR_MESSAGE\"}}\n    //                         undefined,\n    //                         request.language\n\n    //                     );\n        \n                       \n    //                 }\n    //         }\n            \n    //     } \n    // }\n\n\n\n\n    welcomeAgentOnJoin(request, member) {\n        var botId = BotFromParticipant.getBotFromParticipants(request.participants);\n        if (!botId) {                        \n        // if (!request.department.id_bot) {\n            \n            winston.debug(\"ConciergeBot send agent welcome bot message\");     \n          \n              // TODO if (request is assigned allora manda we are putting inn touch )\n            // controlla dopo reassing\n            if (request.status == RequestConstants.ASSIGNED) {\n                if (request.participants.length>0) {\n                    \n                    // if (request.availableAgents.length==0) {\n                       \n                    var updateconversationfor = request.participants;\n\n                    if (member) {\n                        updateconversationfor = [member];\n                    }\n\n                    let touchText = request.first_text;\n                    if (touchText) {  //first_text can be empty for type image\n                        touchText = touchText.replace(/[\\n\\r]+/g, ' '); //replace new line with space\n                    }\n                    if (touchText.length > 30) {\n                        touchText = touchText.substring(0,30);\n                    }\n                    if (request.subject) {\n                        touchText = request.subject;\n                    }\n\n                    \n                    // touchText.replace(String.fromCharCode(92),\"\"); //Bugfix when a conversation has a first_text with \\agent\n                    touchText = touchText.replace(/\\\\/g, \"\");\n\n                    winston.debug(\"touchText: \" + touchText);\n\n                        // messageService.send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type);\n                        messageService.send(\n                            'system', \n                            'Bot',                                     \n                            request.request_id,\n                            i8nUtil.getMessage(\"TOUCHING_OPERATOR\", request.language, MessageConstants.LABELS)+\": \" + touchText, \n                            request.id_project,\n                            'system',                             \n                            {subtype:\"info\", \"updateconversation\" : true, \"updateconversationfor\":updateconversationfor, forcenotification: true,  messagelabel: {key: \"TOUCHING_OPERATOR\"}},\n                            undefined,\n                            request.language\n\n                        );\n                    \n                                           \n            }\n            \n        } \n    }\n    }\n\n    \n}\n\nvar conciergeBot = new ConciergeBot();\nmodule.exports = conciergeBot;\n"
  },
  {
    "path": "pubmodules/s/index.js",
    "content": "const _0x26e2=['exports'];(function(_0x151aec,_0x26e2c5){const _0x5ab999=function(_0x4bca08){while(--_0x4bca08){_0x151aec['push'](_0x151aec['shift']());}};_0x5ab999(++_0x26e2c5);}(_0x26e2,0xbc));const _0x5ab9=function(_0x151aec,_0x26e2c5){_0x151aec=_0x151aec-0x0;let _0x5ab999=_0x26e2[_0x151aec];return _0x5ab999;};const stripeRoute=require('./stripe/index');module[_0x5ab9('0x0')]={'stripeRoute':stripeRoute};\n//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBheW1lbnRzL2luZGV4LmpzIl0sIm5hbWVzIjpbInN0cmlwZVJvdXRlIiwicmVxdWlyZSIsIm1vZHVsZSJdLCJtYXBwaW5ncyI6IjJUQUFBLE1BQU1BLFdBQUEsQ0FBY0MsT0FBQSxDLGdCQUFBLENBQXBCLENBQ0FDLE1BQUEsQyxjQUFBLEVBQWlCLEMsYUFBQyxDQUFZRixXQUFiLENBQWpCIiwic291cmNlc0NvbnRlbnQiOlsiY29uc3Qgc3RyaXBlUm91dGUgPSByZXF1aXJlKFwiLi9zdHJpcGUvaW5kZXhcIik7XG5tb2R1bGUuZXhwb3J0cyA9IHtzdHJpcGVSb3V0ZTpzdHJpcGVSb3V0ZX07XG5cbi8vIHYxNC4xOC4wIl0sImZpbGUiOiJwYXltZW50cy9pbmRleC5qcyJ9\n"
  },
  {
    "path": "pubmodules/s/models/subscription-payment.js",
    "content": "var _0x3134=['mongoose','exports'];(function(_0x12729e,_0x313437){var _0xaba6d1=function(_0x390bc4){while(--_0x390bc4){_0x12729e['push'](_0x12729e['shift']());}};_0xaba6d1(++_0x313437);}(_0x3134,0xb1));var _0xaba6=function(_0x12729e,_0x313437){_0x12729e=_0x12729e-0x0;var _0xaba6d1=_0x3134[_0x12729e];return _0xaba6d1;};var mongoose=require(_0xaba6('0x1'));var Schema=mongoose['Schema'];var SubscriptionPayment=new Schema({'subscription_id':{'type':String},'project_id':{'type':String},'object':{'type':Object},'user_id':{'type':String},'plan_name':{'type':String},'stripe_event':{'type':String},'agents':{'type':Number}},{'timestamps':!![]});module[_0xaba6('0x0')]=mongoose['model']('subscription-payment',SubscriptionPayment);\n//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBheW1lbnRzL21vZGVscy9zdWJzY3JpcHRpb24tcGF5bWVudC5qcyJdLCJuYW1lcyI6WyJtb25nb29zZSIsInJlcXVpcmUiLCJTY2hlbWEiLCJTdWJzY3JpcHRpb25QYXltZW50IiwiU3RyaW5nIiwiT2JqZWN0IiwiTnVtYmVyIiwibW9kdWxlIl0sIm1hcHBpbmdzIjoiZ1VBQUEsSUFBSUEsUUFBQSxDQUFXQyxPQUFBLEMsY0FBQSxDQUFmLENBQ0EsSUFBSUMsTUFBQSxDQUFTRixRQUFBLEMsUUFBQSxDQUFiLENBR0EsSUFBSUcsbUJBQUEsQ0FBc0IsSUFBSUQsTUFBSixDQUFXLEMsaUJBRW5DLENBQWlCLEMsTUFDZixDQUFNRSxNQURTLENBRmtCLEMsWUFNbkMsQ0FBWSxDLE1BQ1YsQ0FBTUEsTUFESSxDQU51QixDLFFBVW5DLENBQVEsQyxNQUNOLENBQU1DLE1BREEsQ0FWMkIsQyxTQWNuQyxDQUFTLEMsTUFDUCxDQUFNRCxNQURDLENBZDBCLEMsV0FrQm5DLENBQVcsQyxNQUNULENBQUtBLE1BREksQ0FsQndCLEMsY0FxQm5DLENBQWMsQyxNQUNaLENBQU1BLE1BRE0sQ0FyQnFCLEMsUUF5Qm5DLENBQVEsQyxNQUNOLENBQU1FLE1BREEsQ0F6QjJCLENBQVgsQ0E2QnZCLEMsWUFDQyxDLElBREQsQ0E3QnVCLENBQTFCLENBa0NBQyxNQUFBLEMsY0FBQSxFQUFpQlAsUUFBQSxDLE9BQUEsRSxzQkFBQSxDQUF1Q0csbUJBQXZDLENBQWpCIiwic291cmNlc0NvbnRlbnQiOlsidmFyIG1vbmdvb3NlID0gcmVxdWlyZSgnbW9uZ29vc2UnKTtcbnZhciBTY2hlbWEgPSBtb25nb29zZS5TY2hlbWE7XG5cblxudmFyIFN1YnNjcmlwdGlvblBheW1lbnQgPSBuZXcgU2NoZW1hKHtcblxuICBzdWJzY3JpcHRpb25faWQ6IHtcbiAgICB0eXBlOiBTdHJpbmcsXG4gICAgLy8gcmVxdWlyZWQ6IHRydWVcbiAgfSxcbiAgcHJvamVjdF9pZDoge1xuICAgIHR5cGU6IFN0cmluZyxcbiAgICAvLyByZXF1aXJlZDogdHJ1ZVxuICB9LFxuICBvYmplY3Q6IHtcbiAgICB0eXBlOiBPYmplY3QsXG4gICAgLy8gcmVxdWlyZWQ6IHRydWVcbiAgfSxcbiAgdXNlcl9pZDoge1xuICAgIHR5cGU6IFN0cmluZyxcbiAgICAvLyByZXF1aXJlZDogdHJ1ZVxuICB9LFxuICBwbGFuX25hbWU6IHtcbiAgICB0eXBlOlN0cmluZ1xuICB9LFxuICBzdHJpcGVfZXZlbnQ6IHtcbiAgICB0eXBlOiBTdHJpbmcsXG4gICAgLy8gcmVxdWlyZWQ6IHRydWVcbiAgfSxcbiAgYWdlbnRzOiB7XG4gICAgdHlwZTogTnVtYmVyLFxuICAgIC8vIHJlcXVpcmVkOiB0cnVlXG4gIH1cbn0sIHtcbiAgICB0aW1lc3RhbXBzOiB0cnVlXG4gIH1cbik7XG5cbm1vZHVsZS5leHBvcnRzID0gbW9uZ29vc2UubW9kZWwoJ3N1YnNjcmlwdGlvbi1wYXltZW50JywgU3Vic2NyaXB0aW9uUGF5bWVudCk7Il0sImZpbGUiOiJwYXltZW50cy9tb2RlbHMvc3Vic2NyaXB0aW9uLXBheW1lbnQuanMifQ==\n"
  },
  {
    "path": "pubmodules/s/stripe/index.js",
    "content": "var _0x4ef7=['--\\x20>\\x20cancelsubscription\\x20\\x20err\\x20','error','detach','save','Webhook\\x20Error:\\x20','free','get\\x20PaymentMethods\\x20list\\x20req.params\\x20>\\x20customer_id\\x20','subscription_create','detachPaymentFunct\\x20>\\x20paymentMethodid\\x20','***\\x20***\\x20customer.subscription.deleted\\x20','get\\x20PaymentMethods\\x20list\\x20>\\x20paymentMethods\\x20','customers','env','YYYY-MM-DDTHH:mm:ss.SSS','data','rawBody','»»»\\x20»»»\\x20cancelsubscription\\x20projectid','invoice_settings','stripe\\x20endpointSecret','***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20subscription\\x20ID:\\x20','PAYMENT_STRIPE_APIKEY','***\\x20***\\x20checkout.session.completed\\x20-\\x20getSubscritionById\\x20err\\x20','credit_card_num','card','credit_card_cvc','/customer/:projectid','--\\x20>\\x20get\\x20customer\\x20from\\x20db\\x20-\\x20subscription\\x20>\\x20customer\\x20id\\x20','forEach','catch','Error\\x20saving\\x20object.','customer.subscription.deleted\\x20event\\x20','***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20subsptn_payment:\\x20','subEnd','lines','***\\x20***\\x20checkout.session.completed\\x20-\\x20getSubscription\\x20***\\x20end\\x20***\\x20:\\x20','body-parser','get\\x20PaymentMethods\\x20list\\x20>\\x20paymentMethods\\x20>\\x20default_payment_method_id\\x20','--\\x20>\\x20checkoutSession\\x20get\\x20by\\x20id\\x20from\\x20stripe\\x20','total_count','subscriptions','stripe-signature','exports','../models/subscription-payment','webhooks','body','customer.subscription.deleted\\x20profile\\x20to\\x20update\\x20(event.data.object.canceled_at)\\x20','passport','stripe\\x20apiSecretKey','invoice.payment_succeeded','customer.subscription.deleted\\x20subscriptionPayment\\x20user\\x20id:\\x20','application/json','saveOnDB\\x20plan_name','***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20subsptn_payment\\x20typeof\\x20subsptn_payment:\\x20','\\x20!!!!!!!!\\x20HI\\x20!!!!!!!!\\x20invoice.payment_succeeded','»»»\\x20»»»\\x20\\x20paymentMethod','split','sort','stripe\\x20seats_numAsString:\\x20','period','saveFirstInvoicePaymentSucceeded\\x20stripe_event','message','moment','put','../../../config/winston','expiration_date_year','update','detachPaymentFunc\\x20result\\x20','--\\x20>\\x20subscription\\x20get\\x20by\\x20id\\x20freq.params.subscriptionid\\x20','***\\x20***\\x20checkout.session.completed\\x20-\\x20getSubscritionById\\x20subscr\\x20object_type:\\x20','Types','savedSubscriptionPayment\\x20>>\\x20','debug','payment','end','constructEvent','params','subscription','del','/checkoutSession/:sessionid','current_period_start','stripe\\x20seats_num:\\x20','stripe\\x20plan_name:\\x20','subscriptionId','info','USECASE\\x20\\x20invoice.payment_succeeded\\x20\\x20billing_reason\\x20subscription_create\\x20','get','\\x20typeof\\x20','***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20end:\\x20','agents','retrieve','subscriptionid','findOne','type','checkout','client_reference_id','checkout.session.completed','express','json','/stripesubs/:subscriptionid','basic','../../../models/project','***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20BILLING\\x20REASON\\x20','raw','object','***\\x20***\\x20subscriptionEnd\\x20','***\\x20***\\x20»»»\\x20»»»\\x20customer.subscription.deleted\\x20subscriptionPayment\\x20','/cancelsubscription','--\\x20>\\x20get\\x20customer\\x20from\\x20db\\x20>\\x20customer\\x20+\\x20paymentMethods\\x20','--\\x20>\\x20updatesubscription\\x20\\x20project\\x20','post','»»»\\x20»»»\\x20updatesubscription\\x20price','userid','***\\x20***\\x20checkout.session.completed\\x20-\\x20getSubscritionById\\x20***\\x20start\\x20***\\x20:\\x20','saveOnDB\\x20seats_num','paymentMethods','project_id','***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20\\x20find\\x20Project\\x20','list','then','***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20-\\x20err\\x20','savedSubscriptionPayment\\x20','attach','last_stripe_event','»»»\\x20»»»\\x20\\x20paymentMethod\\x20attached\\x20','billing_reason','sessionid','***\\x20***\\x20!!!!!!!!!!!!!!!!!!!!!!\\x20checkout.session.completed\\x20-\\x20subscription\\x20ID:\\x20','headers','--\\x20>\\x20updatesubscription\\x20Error\\x20getting\\x20project\\x20','****\\x20Stripe\\x20error\\x20constructEvent:\\x20','stripe\\x20apiKey','saveFirstInvoicePaymentSucceeded\\x20subscriptionid','find','default_payment_method','»»»\\x20»»»\\x20get\\x20customer\\x20from\\x20db\\x20-\\x20projectid','jwt','--\\x20>\\x20GET\\x20SUBSCRIPTION\\x20PAYMENTS\\x20ERRORt\\x20','***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20subsptn_payment\\x20>\\x20project_id:\\x20','customerid','/payment_methods/:customerid','stripe','log','stripe\\x20project_id:\\x20','»»»»\\x20stripe\\x20endpointSecret:\\x20','unix','stripe\\x20client_reference_id','status','customer.subscription.deleted\\x20profile\\x20to\\x20update\\x20','/updatesubscription','--\\x20>\\x20checkoutSession\\x20params.sessionid\\x20','sessions','findByIdAndUpdate','projectid','start','profile','authenticate','--\\x20>\\x20cancelsubscription\\x20Error\\x20getting\\x20project\\x20','subscription.deleted','get\\x20PaymentMethods\\x20list\\x20req.params','../../../middleware/valid-token','exec','PAYMENT_STRIPE_API_SECRET_KEY','user_id','stripe\\x20checkout.session.completed','../../../middleware/passport','»»»\\x20»»»\\x20get\\x20customer\\x20from\\x20db\\x20','»»»\\x20»»»\\x20\\x20update\\x20customer\\x20-\\x20customerid\\x20from\\x20params\\x20','»»»\\x20»»»\\x20cancelsubscription\\x20userid','***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20index:\\x20','ObjectId','Sandbox','expiration_date_month','»»»\\x20»»»\\x20updatesubscription\\x20projectid','***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20start:\\x20','--\\x20>\\x20cancelsubscription\\x20confirmation\\x20','customer','PAYMENT_STRIPE_SECRET','--\\x20>\\x20subscription\\x20get\\x20by\\x20id\\x20from\\x20stripe\\x20\\x20err\\x20','canceled_at','format','customer.subscription.deleted\\x20subscriptionPayment\\x20project\\x20id:\\x20','send'];(function(_0x418770,_0x4ef704){var _0x15b190=function(_0x28a7c7){while(--_0x28a7c7){_0x418770['push'](_0x418770['shift']());}};_0x15b190(++_0x4ef704);}(_0x4ef7,0x177));var _0x15b1=function(_0x418770,_0x4ef704){_0x418770=_0x418770-0x0;var _0x15b190=_0x4ef7[_0x418770];return _0x15b190;};var express=require(_0x15b1('0x55'));var router=express['Router']();var winston=require(_0x15b1('0x34'));var Project=require(_0x15b1('0x59'));var SubscriptionPayment=require(_0x15b1('0x1f'));var moment=require(_0x15b1('0x32'));var mongoose=require('mongoose');var passport=require(_0x15b1('0x23'));require(_0x15b1('0x99'))(passport);var validtoken=require(_0x15b1('0x94'));const apiKey=process[_0x15b1('0x1')][_0x15b1('0x9')];winston[_0x15b1('0x3c')](_0x15b1('0x77')+apiKey);const stripe=require(_0x15b1('0x81'))(apiKey);const endpointSecret=process[_0x15b1('0x1')][_0x15b1('0xa5')];winston[_0x15b1('0x3c')](_0x15b1('0x7')+endpointSecret);const apiSecretKey=process[_0x15b1('0x1')][_0x15b1('0x96')];winston[_0x15b1('0x3c')](_0x15b1('0x24')+apiSecretKey);const bodyParser=require(_0x15b1('0x18'));router[_0x15b1('0x62')]('/webhook',bodyParser[_0x15b1('0x5b')]({'type':_0x15b1('0x27')}),function(_0x1bc881,_0x231607){winston[_0x15b1('0x3c')](_0x15b1('0x84')+endpointSecret);winston[_0x15b1('0x3c')]('»»»»\\x20stripe\\x20apiKey:\\x20'+apiKey);winston[_0x15b1('0x3c')](_0x15b1('0x24')+apiSecretKey);const _0x175799=_0x1bc881[_0x15b1('0x74')][_0x15b1('0x1d')];winston[_0x15b1('0x3c')]('stripe\\x20sig:\\x20',_0x175799);let _0x52fcd0;try{_0x52fcd0=stripe[_0x15b1('0x20')][_0x15b1('0x3f')](_0x1bc881[_0x15b1('0x4')],_0x175799,endpointSecret);}catch(_0x5f3b19){winston[_0x15b1('0xac')](_0x15b1('0x76'),_0x5f3b19[_0x15b1('0x31')]);return _0x231607[_0x15b1('0x87')](0x190)[_0x15b1('0xaa')](_0x15b1('0xaf')+_0x5f3b19[_0x15b1('0x31')]);}if(_0x52fcd0[_0x15b1('0x51')]==='checkout.session.completed'){winston['debug']('!!!!!!!!\\x20HI\\x20!!!!!!!!\\x20checkout.session.completed');const _0x2787f7=_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')];winston[_0x15b1('0x48')](_0x15b1('0x98'),_0x2787f7);var _0x1e8a3e=_0x2787f7[_0x15b1('0x53')];winston[_0x15b1('0x48')](_0x15b1('0x86'),_0x1e8a3e);var _0x5e28e2=_0x1e8a3e[_0x15b1('0x2c')]('_')[0x0];winston[_0x15b1('0x48')]('stripe\\x20user_id:'+_0x5e28e2);var _0x3d054d=_0x1e8a3e['split']('_')[0x1];winston['info'](_0x15b1('0x83')+_0x3d054d);var _0x282810=_0x1e8a3e['split']('_')[0x2];winston[_0x15b1('0x48')](_0x15b1('0x46')+_0x282810);var _0x310977=_0x1e8a3e[_0x15b1('0x2c')]('_')[0x3];winston['info'](_0x15b1('0x45')+_0x310977+'typeof\\x20',typeof _0x310977);var _0x1a282b=Number(_0x310977);winston[_0x15b1('0x48')](_0x15b1('0x2e')+_0x1a282b+_0x15b1('0x4b'),typeof _0x1a282b);var _0x4c51c2=_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')]['subscription'];winston[_0x15b1('0x48')](_0x15b1('0x73'),_0x4c51c2);getSubscritionById(_0x4c51c2)['then'](function(_0x3813f8){var _0x4b3f99=_0x3813f8['object'];winston[_0x15b1('0x48')](_0x15b1('0x39'),_0x4b3f99);var _0x3a8f3e=moment[_0x15b1('0x85')](_0x3813f8[_0x15b1('0x44')])[_0x15b1('0xa8')](_0x15b1('0x2'));winston[_0x15b1('0x48')](_0x15b1('0x65'),_0x3a8f3e);var _0x22a488=moment[_0x15b1('0x85')](_0x3813f8['current_period_end'])[_0x15b1('0xa8')](_0x15b1('0x2'));winston[_0x15b1('0x48')](_0x15b1('0x17'),_0x22a488);var _0x403371={'profile':{'name':_0x282810,'type':_0x15b1('0x3d'),'subscription_creation_date':_0x3a8f3e,'subStart':_0x3a8f3e,'subEnd':_0x22a488,'subscriptionId':_0x4c51c2,'last_stripe_event':_0x52fcd0['type'],'agents':_0x1a282b}};updateProjectProfile(_0x3d054d,_0x403371,'checkout.session.completed');saveOnDB(_0x4c51c2,_0x3d054d,_0x3813f8,_0x5e28e2,_0x52fcd0[_0x15b1('0x51')],_0x282810,_0x1a282b);})['catch'](function(_0x28d01d){winston[_0x15b1('0xac')](_0x15b1('0xa'),_0x28d01d);});}if(_0x52fcd0[_0x15b1('0x51')]==='invoice.payment_succeeded'){winston['info'](_0x15b1('0x2a'));winston['info'](_0x15b1('0x5a'),_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')][_0x15b1('0x71')]);var _0x1fdf5b=_0x52fcd0['data'][_0x15b1('0x5c')][_0x15b1('0x16')][_0x15b1('0x1b')];winston[_0x15b1('0x48')]('***\\x20***\\x20invoice.payment_succeeded\\x20-\\x20linesNum:\\x20',_0x1fdf5b);var _0xdce2a8=_0x1fdf5b-0x1;winston[_0x15b1('0x48')](_0x15b1('0x9d'),_0xdce2a8);var _0x467c8c=moment[_0x15b1('0x85')](_0x52fcd0['data'][_0x15b1('0x5c')][_0x15b1('0x16')][_0x15b1('0x3')][_0xdce2a8]['period'][_0x15b1('0x8e')])[_0x15b1('0xa8')](_0x15b1('0x2'));winston[_0x15b1('0x48')](_0x15b1('0xa2'),_0x467c8c);var _0x33ea7e=moment[_0x15b1('0x85')](_0x52fcd0[_0x15b1('0x3')]['object']['lines'][_0x15b1('0x3')][_0xdce2a8][_0x15b1('0x2f')][_0x15b1('0x3e')])[_0x15b1('0xa8')](_0x15b1('0x2'));winston[_0x15b1('0x48')](_0x15b1('0x4c'),_0x33ea7e);var _0x4c51c2=_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')][_0x15b1('0x41')];winston[_0x15b1('0x48')](_0x15b1('0x8'),_0x4c51c2);if(_0x4c51c2==null){_0x4c51c2=_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')]['lines'][_0x15b1('0x3')][0x0][_0x15b1('0x41')];winston[_0x15b1('0x48')](_0x15b1('0x8'),_0x4c51c2);}if(_0x52fcd0['data']['object']['billing_reason']!==_0x15b1('0xb2')){getSubByIdAndCheckoutSessionCompletedEvnt(_0x4c51c2)[_0x15b1('0x6b')](function(_0x3ff5d3){winston[_0x15b1('0x48')](_0x15b1('0x14'),_0x3ff5d3);winston[_0x15b1('0x48')](_0x15b1('0x29'),typeof _0x3ff5d3);if(_0x3ff5d3){var _0x56ad04=_0x3ff5d3[_0x15b1('0x68')];winston[_0x15b1('0x48')](_0x15b1('0x7e'),_0x56ad04);Project[_0x15b1('0x50')]({'_id':_0x56ad04},function(_0x50aa7c,_0x320046){if(_0x50aa7c){winston[_0x15b1('0x48')](_0x15b1('0x69'),_0x50aa7c);return _0x50aa7c;}if(_0x320046){winston[_0x15b1('0x48')]('***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20\\x20project\\x20',_0x320046);winston[_0x15b1('0x48')]('***\\x20***\\x20getSubByIdAndCheckoutSessionCompletedEvnt\\x20\\x20project\\x20>\\x20profile',_0x320046[_0x15b1('0x8f')]);var _0x57de76=_0x320046['profile'][_0x15b1('0x4d')];var _0x182389=_0x320046[_0x15b1('0x8f')]['name'];var _0x47fbda=_0x3ff5d3[_0x15b1('0x97')];let _0x11b7fe=_0x320046['profile'];_0x11b7fe['name']=_0x182389;_0x11b7fe[_0x15b1('0x51')]=_0x15b1('0x3d');_0x11b7fe['subStart']=_0x467c8c;_0x11b7fe[_0x15b1('0x15')]=_0x33ea7e;_0x11b7fe['subscriptionId']=_0x4c51c2;_0x11b7fe[_0x15b1('0x6f')]=_0x52fcd0['type'];var _0x4b1a4e={'profile':_0x11b7fe};updateProjectProfile(_0x56ad04,_0x4b1a4e,_0x15b1('0x25'));saveOnDB(_0x4c51c2,_0x56ad04,_0x52fcd0,_0x47fbda,_0x52fcd0['type'],_0x182389,_0x57de76);}});}})[_0x15b1('0x11')](function(_0x36b60b){winston[_0x15b1('0xac')](_0x15b1('0x6c'),_0x36b60b);});}else if(_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')][_0x15b1('0x71')]==='subscription_create'){console[_0x15b1('0x82')](_0x15b1('0x49'));saveFirstInvoicePaymentSucceeded(_0x4c51c2,_0x52fcd0,_0x52fcd0['type']);}}if(_0x52fcd0['type']==='customer.subscription.deleted'){winston[_0x15b1('0x48')]('\\x20!!!!!!!!\\x20HI\\x20!!!!!!!!!!!\\x20customer.subscription.deleted');winston[_0x15b1('0x48')](_0x15b1('0x13'),_0x52fcd0);var _0x4c51c2=_0x52fcd0[_0x15b1('0x3')]['object']['id'];winston['info']('***\\x20***\\x20subscription\\x20ID\\x20',_0x4c51c2);var _0x3bdb9c=moment['unix'](_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')]['canceled_at'])[_0x15b1('0xa8')](_0x15b1('0x2'));winston[_0x15b1('0x48')](_0x15b1('0x5d'),_0x3bdb9c);getSubByIdAndCheckoutSessionCompletedEvnt(_0x4c51c2)[_0x15b1('0x6b')](function(_0x383645){winston[_0x15b1('0x48')](_0x15b1('0x5e'),_0x383645);if(_0x383645){var _0x39f7d8=_0x383645[_0x15b1('0x68')];winston[_0x15b1('0x48')](_0x15b1('0xa9'),_0x39f7d8);var _0x52e503=_0x383645[_0x15b1('0x97')];winston['info'](_0x15b1('0x26'),_0x52e503);}var _0x4acbc1={'profile':{'subscriptionId':_0x4c51c2,'name':_0x15b1('0x9f'),'type':_0x15b1('0xb0'),'agents':0x1,'last_stripe_event':_0x52fcd0[_0x15b1('0x51')],'subEnd':_0x3bdb9c}};winston['info'](_0x15b1('0x22'),_0x52fcd0[_0x15b1('0x3')]['object'][_0x15b1('0xa7')]);winston['info'](_0x15b1('0x88'),_0x4acbc1[_0x15b1('0x8f')]);updateProjectProfile(_0x39f7d8,_0x4acbc1,_0x15b1('0x92'));saveOnDB(_0x4c51c2,_0x39f7d8,_0x52fcd0[_0x15b1('0x3')][_0x15b1('0x5c')],_0x52e503,_0x52fcd0['type'],_0x282810,0x1);})[_0x15b1('0x11')](function(_0x2a1a3a){winston['error'](_0x15b1('0xb4'),_0x2a1a3a);});}_0x231607[_0x15b1('0x56')]({'received':!![]});});function getSubByIdAndCheckoutSessionCompletedEvnt(_0x430853){return new Promise(function(_0x365ecb,_0x52206a){SubscriptionPayment[_0x15b1('0x50')]({'subscription_id':_0x430853,'stripe_event':'checkout.session.completed'},function(_0x2dc6c6,_0x3a269b){if(_0x2dc6c6)_0x52206a(_0x2dc6c6);var _0x88dbce=_0x3a269b;_0x365ecb(_0x88dbce);});});};function getSubscritionById(_0xb497f4){return new Promise(function(_0x4d66d4,_0x3e3890){const _0x32d11c=require(_0x15b1('0x81'))(apiSecretKey);_0x32d11c[_0x15b1('0x1c')][_0x15b1('0x4e')](_0xb497f4,function(_0x112286,_0x534d58){if(_0x112286)_0x3e3890(_0x112286);var _0x534d58=_0x534d58;_0x4d66d4(_0x534d58);});});};function updateProjectProfile(_0x26b5d9,_0x36c121,_0xf15e2d){Project[_0x15b1('0x8c')](_0x26b5d9,_0x36c121,{'new':!![],'upsert':!![]},function(_0x5ab848,_0x4bec81){if(_0x5ab848){winston[_0x15b1('0xac')]('updateProjectProfile\\x20Error\\x20',_0x5ab848);}else{winston[_0x15b1('0x3c')](_0x4bec81);}});}function saveOnDB(_0x462571,_0x47f4eb,_0x696fb9,_0x51275d,_0x27b4c5,_0x4e92fa,_0x464282){winston[_0x15b1('0x48')](_0x15b1('0x28'),_0x4e92fa);winston['info'](_0x15b1('0x66'),_0x464282);var _0x2266ff=new SubscriptionPayment({'_id':new mongoose[(_0x15b1('0x3a'))][(_0x15b1('0x9e'))](),'subscription_id':_0x462571,'project_id':_0x47f4eb,'user_id':_0x51275d,'stripe_event':_0x27b4c5,'plan_name':_0x4e92fa,'agents':_0x464282,'object':_0x696fb9});_0x2266ff['save'](function(_0x325b75,_0x1fb921){if(_0x325b75){winston[_0x15b1('0xac')]('---\\x20>\\x20ERROR\\x20',_0x325b75);return res[_0x15b1('0x87')](0x1f4)['send']({'success':![],'msg':_0x15b1('0x12')});}winston[_0x15b1('0x48')](_0x15b1('0x6d'),_0x1fb921);});}function saveFirstInvoicePaymentSucceeded(_0x19242c,_0x27ead3,_0x37ca2c){console[_0x15b1('0x82')](_0x15b1('0x78'),_0x19242c);console[_0x15b1('0x82')](_0x15b1('0x30'),_0x37ca2c);var _0x1be6d8=new SubscriptionPayment({'_id':new mongoose[(_0x15b1('0x3a'))]['ObjectId'](),'subscription_id':_0x19242c,'stripe_event':_0x37ca2c,'object':_0x27ead3});_0x1be6d8[_0x15b1('0xae')](function(_0x26a88e,_0x233e97){if(_0x26a88e){winston['error']('---\\x20>\\x20ERROR\\x20',_0x26a88e);return res[_0x15b1('0x87')](0x1f4)[_0x15b1('0xaa')]({'success':![],'msg':_0x15b1('0x12')});}console['log'](_0x15b1('0x3b'),_0x233e97);});}router[_0x15b1('0x33')](_0x15b1('0x5f'),[passport[_0x15b1('0x90')]([_0x15b1('0x58'),_0x15b1('0x7c')],{'session':![]}),validtoken],function(_0x2cae72,_0x569a6d){var _0x2a03f2=_0x2cae72[_0x15b1('0x21')][_0x15b1('0x8d')];var _0x2d1562=_0x2cae72[_0x15b1('0x21')][_0x15b1('0x64')];winston[_0x15b1('0x48')](_0x15b1('0x5'),_0x2a03f2);winston['info'](_0x15b1('0x9c'),_0x2d1562);Project[_0x15b1('0x50')]({'_id':_0x2a03f2},function(_0x708b43,_0x3bbc53){if(_0x708b43){winston[_0x15b1('0xac')](_0x15b1('0x91'),_0x708b43);return _0x708b43;}if(_0x3bbc53){winston[_0x15b1('0x48')]('--\\x20>\\x20cancelsubscription\\x20\\x20project\\x20',_0x3bbc53);var _0x1c6e9c=_0x3bbc53[_0x15b1('0x8f')][_0x15b1('0x47')];const _0x1100ec=require(_0x15b1('0x81'))(apiSecretKey);_0x1100ec[_0x15b1('0x1c')][_0x15b1('0x42')](_0x1c6e9c,function(_0x377195,_0x1c2788){if(_0x377195){winston[_0x15b1('0xac')](_0x15b1('0xab'),_0x377195);return _0x569a6d['status'](0x1f4)[_0x15b1('0xaa')]({'success':![],'msg':_0x377195});}winston[_0x15b1('0x48')](_0x15b1('0xa3'),_0x1c2788);_0x569a6d[_0x15b1('0x56')](_0x1c2788);});}});});router[_0x15b1('0x33')](_0x15b1('0x89'),[passport[_0x15b1('0x90')](['basic',_0x15b1('0x7c')],{'session':![]}),validtoken],function(_0x2ecc24,_0x4caea2){var _0x10b790=_0x2ecc24[_0x15b1('0x21')][_0x15b1('0x8d')];var _0x5f0d84=_0x2ecc24[_0x15b1('0x21')][_0x15b1('0x64')];var _0x2b520e=_0x2ecc24[_0x15b1('0x21')]['price'];winston['info'](_0x15b1('0xa1'),_0x10b790);winston[_0x15b1('0x48')]('»»»\\x20»»»\\x20updatesubscription\\x20userid',_0x5f0d84);winston[_0x15b1('0x48')](_0x15b1('0x63'),_0x2b520e);const _0x191994=require(_0x15b1('0x81'))(apiSecretKey);Project[_0x15b1('0x50')]({'_id':_0x10b790},function(_0x3c37b6,_0x25d605){if(_0x3c37b6){winston[_0x15b1('0xac')](_0x15b1('0x75'),_0x3c37b6);return _0x3c37b6;}if(_0x25d605){winston[_0x15b1('0x48')](_0x15b1('0x61'),_0x25d605);var _0x14da8b=_0x25d605['profile'][_0x15b1('0x47')];_0x191994[_0x15b1('0x1c')][_0x15b1('0x36')](_0x14da8b);}});});router[_0x15b1('0x4a')]('/:subscriptionid',[passport[_0x15b1('0x90')]([_0x15b1('0x58'),_0x15b1('0x7c')],{'session':![]}),validtoken],function(_0xe18688,_0x32ed43){SubscriptionPayment[_0x15b1('0x79')]({'subscription_id':_0xe18688[_0x15b1('0x40')][_0x15b1('0x4f')]})[_0x15b1('0x2d')]({'object.created':'asc'})[_0x15b1('0x95')](function(_0x186afc,_0x328c41){if(_0x186afc){winston['error'](_0x15b1('0x7d'),project);return _0x32ed43[_0x15b1('0x87')](0x1f4)[_0x15b1('0xaa')]({'success':![],'msg':_0x186afc});}_0x32ed43[_0x15b1('0x56')](_0x328c41);});});router[_0x15b1('0x4a')](_0x15b1('0x57'),[passport[_0x15b1('0x90')]([_0x15b1('0x58'),'jwt'],{'session':![]}),validtoken],function(_0x32af3a,_0x42895b){winston['info'](_0x15b1('0x38'),_0x32af3a[_0x15b1('0x40')][_0x15b1('0x4f')]);var _0x3fb3e4=require(_0x15b1('0x81'))(apiSecretKey);_0x3fb3e4[_0x15b1('0x1c')][_0x15b1('0x4e')](_0x32af3a[_0x15b1('0x40')][_0x15b1('0x4f')],function(_0x240fd4,_0x35d5f1){if(_0x240fd4){winston[_0x15b1('0xac')](_0x15b1('0xa6'),_0x240fd4);return _0x42895b['status'](0x1f4)[_0x15b1('0xaa')]({'success':![],'msg':_0x240fd4});}winston[_0x15b1('0x48')]('--\\x20>\\x20subscription\\x20get\\x20by\\x20id\\x20from\\x20stripe\\x20',_0x35d5f1);_0x42895b[_0x15b1('0x56')](_0x35d5f1);});});router[_0x15b1('0x4a')](_0x15b1('0x43'),[passport[_0x15b1('0x90')](['basic','jwt'],{'session':![]}),validtoken],function(_0x54f287,_0x428ea5){winston[_0x15b1('0x48')](_0x15b1('0x8a'),_0x54f287[_0x15b1('0x40')][_0x15b1('0x72')]);var _0x1357e4=require(_0x15b1('0x81'))(apiSecretKey);_0x1357e4[_0x15b1('0x52')][_0x15b1('0x8b')][_0x15b1('0x4e')](_0x54f287[_0x15b1('0x40')]['sessionid'],function(_0x36f1df,_0x481728){if(_0x36f1df){winston[_0x15b1('0x48')]('--\\x20>\\x20checkoutSession\\x20get\\x20by\\x20id\\x20from\\x20stripe\\x20\\x20err\\x20',_0x36f1df);return _0x428ea5[_0x15b1('0x87')](0x1f4)[_0x15b1('0xaa')]({'success':![],'msg':_0x36f1df});}winston[_0x15b1('0x48')](_0x15b1('0x1a'),_0x481728);_0x428ea5[_0x15b1('0x56')](_0x481728);});});router[_0x15b1('0x4a')](_0x15b1('0xe'),[passport['authenticate']([_0x15b1('0x58'),_0x15b1('0x7c')],{'session':![]}),validtoken],function(_0x1f217f,_0xe893d){winston['debug'](_0x15b1('0x9a'),_0x1f217f['params']);var _0x57d7b5=_0x1f217f[_0x15b1('0x40')]['projectid'];winston['debug'](_0x15b1('0x7b'),_0x57d7b5);SubscriptionPayment['find']({'project_id':_0x57d7b5,'stripe_event':_0x15b1('0x54')},async function(_0x57b2c7,_0x3a1ac2){if(_0x57b2c7){winston['debug']('--\\x20>\\x20get\\x20customer\\x20from\\x20db\\x20-\\x20Error\\x20',_0x57b2c7);return _0x57b2c7;}if(_0x3a1ac2){_0x3a1ac2[0x0][_0x15b1('0x5c')][_0x15b1('0xa4')];winston['debug'](_0x15b1('0xf'),_0x3a1ac2[0x0][_0x15b1('0x5c')][_0x15b1('0xa4')]);const _0x133227=_0x3a1ac2[0x0][_0x15b1('0x5c')][_0x15b1('0xa4')];const _0x3cf78b=require(_0x15b1('0x81'))(apiSecretKey);const _0x1ec856=await _0x3cf78b[_0x15b1('0x0')]['retrieve'](_0x133227);winston[_0x15b1('0x3c')]('--\\x20>\\x20get\\x20customer\\x20from\\x20db\\x20>\\x20customer\\x20from\\x20stripe\\x20API\\x20',_0x1ec856);const _0x27a810=await _0x3cf78b[_0x15b1('0x67')][_0x15b1('0x6a')]({'customer':_0x133227,'type':_0x15b1('0xc')});_0x1ec856['paymentMethods']=_0x27a810;winston['debug'](_0x15b1('0x60'),_0x3a1ac2);_0xe893d[_0x15b1('0x56')](_0x1ec856);}});});router['post']('/customers/:customerid',[passport['authenticate']([_0x15b1('0x58'),'jwt'],{'session':![]}),validtoken],async function(_0x45235a,_0x338697){var _0x2e54f6=_0x45235a[_0x15b1('0x40')][_0x15b1('0x7f')];winston[_0x15b1('0x3c')](_0x15b1('0x9b'),_0x2e54f6);winston[_0x15b1('0x3c')]('»»»\\x20»»»\\x20\\x20update\\x20customer\\x20-\\x20cc\\x20from\\x20body\\x20',_0x45235a[_0x15b1('0x21')]);const _0x211a72=require(_0x15b1('0x81'))(apiSecretKey);let _0x1de67d;try{_0x1de67d=await _0x211a72[_0x15b1('0x67')]['create']({'type':'card','card':{'number':_0x45235a[_0x15b1('0x21')][_0x15b1('0xb')],'exp_month':_0x45235a[_0x15b1('0x21')][_0x15b1('0xa0')],'exp_year':_0x45235a['body'][_0x15b1('0x35')],'cvc':_0x45235a['body'][_0x15b1('0xd')]}});}catch(_0x23fbed){winston[_0x15b1('0xac')]('»»»\\x20»»»\\x20\\x20paymentMethod\\x20create\\x20\\x20error\\x20',_0x23fbed);return _0x338697[_0x15b1('0x87')](0x1f6)[_0x15b1('0xaa')]({'success':![],'msg':_0x23fbed});}winston['debug'](_0x15b1('0x2b'),_0x1de67d);try{const _0x357fdf=await _0x211a72[_0x15b1('0x67')][_0x15b1('0x6e')](_0x1de67d['id'],{'customer':_0x2e54f6});winston[_0x15b1('0x3c')](_0x15b1('0x70'),_0x357fdf);}catch(_0x388221){winston[_0x15b1('0xac')]('»»»\\x20»»»\\x20\\x20paymentMethod\\x20attached\\x20\\x20error\\x20',_0x388221);return _0x338697[_0x15b1('0x87')](0x1f5)[_0x15b1('0xaa')]({'success':![],'msg':_0x388221});}const _0x1dd031=await _0x211a72[_0x15b1('0x0')][_0x15b1('0x36')](_0x2e54f6,{'invoice_settings':{'default_payment_method':_0x1de67d['id']}});_0x338697[_0x15b1('0x56')](_0x1dd031);});router[_0x15b1('0x4a')](_0x15b1('0x80'),[passport[_0x15b1('0x90')](['basic',_0x15b1('0x7c')],{'session':![]}),validtoken],async function(_0x4a1e03,_0x347cbd){winston[_0x15b1('0x48')](_0x15b1('0x93'),_0x4a1e03[_0x15b1('0x40')]);var _0xf4172c=_0x4a1e03[_0x15b1('0x40')][_0x15b1('0x7f')];winston[_0x15b1('0x3c')](_0x15b1('0xb1'),_0xf4172c);const _0x36307d=require(_0x15b1('0x81'))(apiSecretKey);const _0x27a45f=await _0x36307d['customers'][_0x15b1('0x4e')](_0xf4172c);const _0x13af46=_0x27a45f[_0x15b1('0x6')][_0x15b1('0x7a')];let _0x3a54a0;try{_0x3a54a0=await _0x36307d['paymentMethods'][_0x15b1('0x6a')]({'customer':_0xf4172c,'type':_0x15b1('0xc')});winston[_0x15b1('0x3c')](_0x15b1('0xb5'),_0x3a54a0);}catch(_0x468a4c){return _0x347cbd[_0x15b1('0x87')](0x1f5)['send']({'success':![],'msg':_0x468a4c});}winston[_0x15b1('0x3c')](_0x15b1('0x19'),_0x13af46);_0x3a54a0[_0x15b1('0x3')][_0x15b1('0x10')](_0x1e84c8=>{if(_0x1e84c8['id']!==_0x13af46){winston[_0x15b1('0x3c')]('get\\x20PaymentMethods\\x20list\\x20>\\x20paymentMethods\\x20>\\x20paymentMethods.data\\x20',_0x1e84c8['id']);detachPaymentFunc(_0x1e84c8['id'],function(_0x165391){winston[_0x15b1('0x48')](_0x15b1('0x37'),_0x165391);});}});_0x347cbd[_0x15b1('0x56')](_0x3a54a0);});async function detachPaymentFunc(_0x5c72a1,_0x5379eb){winston[_0x15b1('0x3c')](_0x15b1('0xb3'),_0x5c72a1);const _0x3612b3=require(_0x15b1('0x81'))(apiSecretKey);let _0x113a94;try{_0x113a94=await _0x3612b3[_0x15b1('0x67')][_0x15b1('0xad')](_0x5c72a1);_0x5379eb(_0x113a94);}catch(_0x3f8cbf){_0x5379eb(_0x3f8cbf);}}module[_0x15b1('0x1e')]=router;\n//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInBheW1lbnRzL3N0cmlwZS9pbmRleC5qcyJdLCJuYW1lcyI6WyJleHByZXNzIiwicmVxdWlyZSIsInJvdXRlciIsIndpbnN0b24iLCJQcm9qZWN0IiwiU3Vic2NyaXB0aW9uUGF5bWVudCIsIm1vbWVudCIsIm1vbmdvb3NlIiwicGFzc3BvcnQiLCJ2YWxpZHRva2VuIiwiYXBpS2V5IiwicHJvY2VzcyIsInN0cmlwZSIsImVuZHBvaW50U2VjcmV0IiwiYXBpU2VjcmV0S2V5IiwiYm9keVBhcnNlciIsIl8weDFiYzg4MSIsIl8weDIzMTYwNyIsIl8weDE3NTc5OSIsIl8weDUyZmNkMCIsIl8weDVmM2IxOSIsIl8weDI3ODdmNyIsIl8weDFlOGEzZSIsIl8weDVlMjhlMiIsIl8weDNkMDU0ZCIsIl8weDI4MjgxMCIsIl8weDMxMDk3NyIsIl8weDFhMjgyYiIsIk51bWJlciIsIl8weDRjNTFjMiIsImdldFN1YnNjcml0aW9uQnlJZCIsIl8weDM4MTNmOCIsIl8weDRiM2Y5OSIsIl8weDNhOGYzZSIsIl8weDIyYTQ4OCIsIl8weDQwMzM3MSIsInVwZGF0ZVByb2plY3RQcm9maWxlIiwic2F2ZU9uREIiLCJfMHgyOGQwMWQiLCJfMHgxZmRmNWIiLCJfMHhkY2UyYTgiLCJfMHg0NjdjOGMiLCJfMHgzM2VhN2UiLCJnZXRTdWJCeUlkQW5kQ2hlY2tvdXRTZXNzaW9uQ29tcGxldGVkRXZudCIsIl8weDNmZjVkMyIsIl8weDU2YWQwNCIsIl8weDUwYWE3YyIsIl8weDMyMDA0NiIsIl8weDU3ZGU3NiIsIl8weDE4MjM4OSIsIl8weDQ3ZmJkYSIsIl8weDExYjdmZSIsIl8weDRiMWE0ZSIsIl8weDM2YjYwYiIsImNvbnNvbGUiLCJzYXZlRmlyc3RJbnZvaWNlUGF5bWVudFN1Y2NlZWRlZCIsIl8weDNiZGI5YyIsIl8weDM4MzY0NSIsIl8weDM5ZjdkOCIsIl8weDUyZTUwMyIsIl8weDRhY2JjMSIsIl8weDJhMWEzYSIsIl8weDQzMDg1MyIsIlByb21pc2UiLCJfMHgzNjVlY2IiLCJfMHg1MjIwNmEiLCJfMHgyZGM2YzYiLCJfMHgzYTI2OWIiLCJfMHg4OGRiY2UiLCJfMHhiNDk3ZjQiLCJfMHg0ZDY2ZDQiLCJfMHgzZTM4OTAiLCJfMHgzMmQxMWMiLCJfMHgxMTIyODYiLCJfMHg1MzRkNTgiLCJfMHgyNmI1ZDkiLCJfMHgzNmMxMjEiLCJfMHhmMTVlMmQiLCJfMHg1YWI4NDgiLCJfMHg0YmVjODEiLCJfMHg0NjI1NzEiLCJfMHg0N2Y0ZWIiLCJfMHg2OTZmYjkiLCJfMHg1MTI3NWQiLCJfMHgyN2I0YzUiLCJfMHg0ZTkyZmEiLCJfMHg0NjQyODIiLCJfMHgyMjY2ZmYiLCJfMHgzMjViNzUiLCJfMHgxZmI5MjEiLCJyZXMiLCJfMHgxOTI0MmMiLCJfMHgyN2VhZDMiLCJfMHgzN2NhMmMiLCJfMHgxYmU2ZDgiLCJfMHgyNmE4OGUiLCJfMHgyMzNlOTciLCJfMHgyY2FlNzIiLCJfMHg1NjlhNmQiLCJfMHgyYTAzZjIiLCJfMHgyZDE1NjIiLCJfMHg3MDhiNDMiLCJfMHgzYmJjNTMiLCJfMHgxYzZlOWMiLCJfMHgxMTAwZWMiLCJfMHgzNzcxOTUiLCJfMHgxYzI3ODgiLCJfMHgyZWNjMjQiLCJfMHg0Y2FlYTIiLCJfMHgxMGI3OTAiLCJfMHg1ZjBkODQiLCJfMHgyYjUyMGUiLCJfMHgxOTE5OTQiLCJfMHgzYzM3YjYiLCJfMHgyNWQ2MDUiLCJfMHgxNGRhOGIiLCJfMHhlMTg2ODgiLCJfMHgzMmVkNDMiLCJfMHgxODZhZmMiLCJfMHgzMjhjNDEiLCJwcm9qZWN0IiwiXzB4MzJhZjNhIiwiXzB4NDI4OTViIiwiXzB4M2ZiM2U0IiwiXzB4MjQwZmQ0IiwiXzB4MzVkNWYxIiwiXzB4NTRmMjg3IiwiXzB4NDI4ZWE1IiwiXzB4MTM1N2U0IiwiXzB4MzZmMWRmIiwiXzB4NDgxNzI4IiwiXzB4MWYyMTdmIiwiXzB4ZTg5M2QiLCJfMHg1N2Q3YjUiLCJfMHg1N2IyYzciLCJfMHgzYTFhYzIiLCJfMHgxMzMyMjciLCJfMHgzY2Y3OGIiLCJfMHgxZWM4NTYiLCJfMHgyN2E4MTAiLCJfMHg0NTIzNWEiLCJfMHgzMzg2OTciLCJfMHgyZTU0ZjYiLCJfMHgyMTFhNzIiLCJfMHgxZGU2N2QiLCJfMHgyM2ZiZWQiLCJfMHgzNTdmZGYiLCJfMHgzODgyMjEiLCJfMHgxZGQwMzEiLCJfMHg0YTFlMDMiLCJfMHgzNDdjYmQiLCJfMHhmNDE3MmMiLCJfMHgzNjMwN2QiLCJfMHgyN2E0NWYiLCJfMHgxM2FmNDYiLCJfMHgzYTU0YTAiLCJfMHg0NjhhNGMiLCJfMHgxZTg0YzgiLCJkZXRhY2hQYXltZW50RnVuYyIsIl8weDE2NTM5MSIsIl8weDVjNzJhMSIsIl8weDUzNzllYiIsIl8weDM2MTJiMyIsIl8weDExM2E5NCIsIl8weDNmOGNiZiIsIm1vZHVsZSJdLCJtYXBwaW5ncyI6IjI0TEFBQSxJQUFJQSxPQUFBLENBQVVDLE9BQUEsQyxlQUFBLENBQWQsQ0FDQSxJQUFJQyxNQUFBLENBQVNGLE9BQUEsQyxRQUFBLEdBQWIsQ0FFQSxJQUFJRyxPQUFBLENBQVVGLE9BQUEsQyxlQUFBLENBQWQsQ0FFQSxJQUFJRyxPQUFBLENBQVVILE9BQUEsQyxlQUFBLENBQWQsQ0FDQSxJQUFJSSxtQkFBQSxDQUFzQkosT0FBQSxDLGVBQUEsQ0FBMUIsQ0FDQSxJQUFJSyxNQUFBLENBQVNMLE9BQUEsQyxlQUFBLENBQWIsQ0FDQSxJQUFJTSxRQUFBLENBQVdOLE9BQUEsQyxVQUFBLENBQWYsQ0FDQSxJQUFJTyxRQUFBLENBQVdQLE9BQUEsQyxlQUFBLENBQWYsQ0FFQUEsT0FBQSxDLGVBQUEsRUFBd0NPLFFBQXhDLEVBRUEsSUFBSUMsVUFBQSxDQUFhUixPQUFBLEMsZUFBQSxDQUFqQixDQUdBLE1BQU1TLE1BQUEsQ0FBU0MsT0FBQSxDLGNBQUEsRSxjQUFBLENBQWYsQ0FDQVIsT0FBQSxDLGVBQUEsRSxlQUFjLENBQWtCTyxNQUFoQyxFQUVBLE1BQU1FLE1BQUEsQ0FBU1gsT0FBQSxDLGVBQUEsRUFBa0JTLE1BQWxCLENBQWYsQ0FHQSxNQUFNRyxjQUFBLENBQWlCRixPQUFBLEMsY0FBQSxFLGVBQUEsQ0FBdkIsQ0FDQVIsT0FBQSxDLGVBQUEsRSxjQUFjLENBQTBCVSxjQUF4QyxFQUVBLE1BQU1DLFlBQUEsQ0FBZUgsT0FBQSxDLGNBQUEsRSxlQUFBLENBQXJCLENBQ0FSLE9BQUEsQyxlQUFBLEUsZUFBYyxDQUF3QlcsWUFBdEMsRUFFQSxNQUFNQyxVQUFBLENBQWFkLE9BQUEsQyxlQUFBLENBQW5CLENBSUFDLE1BQUEsQyxlQUFBLEUsVUFBQSxDQUF3QmEsVUFBQSxDLGVBQUEsRUFBZSxDLE1BQUUsQyxlQUFGLENBQWYsQ0FBeEIsQ0FBc0UsU0FBVUMsU0FBVixDQUFtQkMsU0FBbkIsQ0FBNkIsQ0FFakdkLE9BQUEsQyxlQUFBLEUsZUFBYyxDQUFpQ1UsY0FBL0MsRUFDQVYsT0FBQSxDLGVBQUEsRSwrQkFBYyxDQUF5Qk8sTUFBdkMsRUFDQVAsT0FBQSxDLGVBQUEsRSxlQUFjLENBQXdCVyxZQUF0QyxFQUVBLE1BQU1JLFNBQUEsQ0FBTUYsU0FBQSxDLGVBQUEsRSxlQUFBLENBQVosQ0FFQWIsT0FBQSxDLGVBQUEsRSxvQkFBQSxDQUE4QmUsU0FBOUIsRUFFQSxJQUFJQyxTQUFKLENBRUEsR0FBSSxDQUNGQSxTQUFBLENBQVFQLE1BQUEsQyxlQUFBLEUsZUFBQSxFQUErQkksU0FBQSxDLGNBQUEsQ0FBL0IsQ0FBZ0RFLFNBQWhELENBQXFETCxjQUFyRCxDQUFSLENBREUsQ0FFRixNQUFPTyxTQUFQLENBQVksQ0FDWmpCLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFvRGlCLFNBQUEsQyxlQUFBLENBQXBELEVBQ0EsT0FBT0gsU0FBQSxDLGVBQUEsRSxLQUFBLEUsZUFBQSxFLGdCQUE0Q0csU0FBQSxDLGVBQUEsQ0FBNUMsQ0FBUCxDQUZZLENBcUJkLEdBQUlELFNBQUEsQyxlQUFBLEksNEJBQUosQ0FBaUQsQ0FDL0NoQixPQUFBLEMsT0FBQSxFLDBEQUFBLEVBRUEsTUFBTWtCLFNBQUEsQ0FBVUYsU0FBQSxDLGNBQUEsRSxlQUFBLENBQWhCLENBQ0FoQixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBa0RrQixTQUFsRCxFQUVBLElBQUlDLFNBQUEsQ0FBc0JELFNBQUEsQyxlQUFBLENBQTFCLENBQ0FsQixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBMkNtQixTQUEzQyxFQUdBLElBQUlDLFNBQUEsQ0FBVUQsU0FBQSxDLGVBQUEsRSxHQUFBLEUsR0FBQSxDQUFkLENBQ0FuQixPQUFBLEMsZUFBQSxFLG9CQUFhLENBQW9Cb0IsU0FBakMsRUFHQSxJQUFJQyxTQUFBLENBQWFGLFNBQUEsQyxPQUFBLEUsR0FBQSxFLEdBQUEsQ0FBakIsQ0FDQW5CLE9BQUEsQyxNQUFBLEUsZUFBYSxDQUF3QnFCLFNBQXJDLEVBRUEsSUFBSUMsU0FBQSxDQUFZSCxTQUFBLEMsT0FBQSxFLEdBQUEsRSxHQUFBLENBQWhCLENBQ0FuQixPQUFBLEMsZUFBQSxFLGVBQWEsQ0FBdUJzQixTQUFwQyxFQUVBLElBQUlDLFNBQUEsQ0FBb0JKLFNBQUEsQyxlQUFBLEUsR0FBQSxFLEdBQUEsQ0FBeEIsQ0FDQW5CLE9BQUEsQyxNQUFBLEUsZUFBYSxDQUF1QnVCLFNBQXZCLEMsWUFBYixDQUFtRSxPQUFPQSxTQUExRSxFQUVBLElBQUlDLFNBQUEsQ0FBWUMsTUFBQSxDQUFPRixTQUFQLENBQWhCLENBQ0F2QixPQUFBLEMsZUFBQSxFLGVBQWEsQ0FBK0J3QixTQUEvQixDLGVBQWIsQ0FBb0UsT0FBT0EsU0FBM0UsRUFFQSxJQUFJRSxTQUFBLENBQWlCVixTQUFBLEMsY0FBQSxFLGVBQUEsRSxjQUFBLENBQXJCLENBQ0FoQixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBOEYwQixTQUE5RixFQUVBQyxrQkFBQSxDQUFtQkQsU0FBbkIsRSxNQUFBLEVBQXdDLFNBQVVFLFNBQVYsQ0FBNEIsQ0FFbEUsSUFBSUMsU0FBQSxDQUFjRCxTQUFBLEMsUUFBQSxDQUFsQixDQUNBNUIsT0FBQSxDLGVBQUEsRSxlQUFBLENBQTZGNkIsU0FBN0YsRUFHQSxJQUFJQyxTQUFBLENBQXdCM0IsTUFBQSxDLGVBQUEsRUFBWXlCLFNBQUEsQyxlQUFBLENBQVosRSxlQUFBLEUsY0FBQSxDQUE1QixDQUNBNUIsT0FBQSxDLGVBQUEsRSxlQUFBLENBQXlGOEIsU0FBekYsRUFHQSxJQUFJQyxTQUFBLENBQXNCNUIsTUFBQSxDLGVBQUEsRUFBWXlCLFNBQUEsQyxvQkFBQSxDQUFaLEUsZUFBQSxFLGNBQUEsQ0FBMUIsQ0FDQTVCLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFvRitCLFNBQXBGLEVBS0EsSUFBSUMsU0FBQSxDQUFPLEMsU0FDVCxDQUFTLEMsTUFDUCxDQUFNVixTQURDLEMsTUFFUCxDLGVBRk8sQyw0QkFHUCxDQUE0QlEsU0FIckIsQyxVQUlQLENBQVVBLFNBSkgsQyxRQUtQLENBQVFDLFNBTEQsQyxnQkFNUCxDQUFnQkwsU0FOVCxDLG1CQU9QLENBQW1CVixTQUFBLEMsTUFBQSxDQVBaLEMsUUFRUCxDQUFRUSxTQVJELENBREEsQ0FBWCxDQWNBUyxvQkFBQSxDQUFxQlosU0FBckIsQ0FBaUNXLFNBQWpDLEMsNEJBQUEsRUFHQUUsUUFBQSxDQUFTUixTQUFULENBQXlCTCxTQUF6QixDQUFxQ08sU0FBckMsQ0FBdURSLFNBQXZELENBQWdFSixTQUFBLEMsZUFBQSxDQUFoRSxDQUE0RU0sU0FBNUUsQ0FBdUZFLFNBQXZGLEVBakNrRSxDQUFwRSxFLE9BQUEsRUFtQ1MsU0FBVVcsU0FBVixDQUFlLENBQ3RCbkMsT0FBQSxDLGVBQUEsRSxjQUFBLENBQThFbUMsU0FBOUUsRUFEc0IsQ0FuQ3hCLEVBN0IrQyxDQTBHakQsR0FBSW5CLFNBQUEsQyxlQUFBLEksMkJBQUosQ0FBZ0QsQ0FFOUNoQixPQUFBLEMsTUFBQSxFLGVBQUEsRUFDQUEsT0FBQSxDLE1BQUEsRSxlQUFBLENBQW9FZ0IsU0FBQSxDLGNBQUEsRSxlQUFBLEUsZUFBQSxDQUFwRSxFQUVBLElBQUlvQixTQUFBLENBQVdwQixTQUFBLEMsTUFBQSxFLGVBQUEsRSxlQUFBLEUsZUFBQSxDQUFmLENBQ0FoQixPQUFBLEMsZUFBQSxFLCtEQUFBLENBQStEb0MsU0FBL0QsRUFFQSxJQUFJQyxTQUFBLENBQVFELFNBQUEsQyxHQUFaLENBQ0FwQyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBNERxQyxTQUE1RCxFQUdBLElBQUlDLFNBQUEsQ0FBd0JuQyxNQUFBLEMsZUFBQSxFQUFZYSxTQUFBLEMsTUFBQSxFLGVBQUEsRSxlQUFBLEUsY0FBQSxFQUE2QnFCLFNBQTdCLEUsUUFBQSxFLGVBQUEsQ0FBWixFLGVBQUEsRSxjQUFBLENBQTVCLENBQ0FyQyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBNERzQyxTQUE1RCxFQUdBLElBQUlDLFNBQUEsQ0FBc0JwQyxNQUFBLEMsZUFBQSxFQUFZYSxTQUFBLEMsY0FBQSxFLFFBQUEsRSxPQUFBLEUsY0FBQSxFQUE2QnFCLFNBQTdCLEUsZUFBQSxFLGVBQUEsQ0FBWixFLGVBQUEsRSxjQUFBLENBQTFCLENBQ0FyQyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBMER1QyxTQUExRCxFQUVBLElBQUliLFNBQUEsQ0FBaUJWLFNBQUEsQyxjQUFBLEUsZUFBQSxFLGVBQUEsQ0FBckIsQ0FDQWhCLE9BQUEsQyxlQUFBLEUsY0FBQSxDQUFzRTBCLFNBQXRFLEVBRUEsR0FBSUEsU0FBQSxFQUFrQixJQUF0QixDQUE0QixDQUMxQkEsU0FBQSxDQUFpQlYsU0FBQSxDLGNBQUEsRSxlQUFBLEUsT0FBQSxFLGNBQUEsRSxHQUFBLEUsZUFBQSxDQUFqQixDQUNBaEIsT0FBQSxDLGVBQUEsRSxjQUFBLENBQXNFMEIsU0FBdEUsRUFGMEIsQ0FLNUIsR0FBSVYsU0FBQSxDLE1BQUEsRSxRQUFBLEUsZ0JBQUEsSSxlQUFKLENBQWdFLENBTTlEd0IseUNBQUEsQ0FBMENkLFNBQTFDLEUsZUFBQSxFQUErRCxTQUFVZSxTQUFWLENBQTJCLENBQ3hGekMsT0FBQSxDLGVBQUEsRSxlQUFBLENBQW9GeUMsU0FBcEYsRUFDQXpDLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUEyRyxPQUFPeUMsU0FBbEgsRUFDQSxHQUFJQSxTQUFKLENBQXFCLENBRW5CLElBQUlDLFNBQUEsQ0FBWUQsU0FBQSxDLGVBQUEsQ0FBaEIsQ0FDQXpDLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFpRzBDLFNBQWpHLEVBRUF6QyxPQUFBLEMsZUFBQSxFQUFnQixDLEtBQUUsQ0FBS3lDLFNBQVAsQ0FBaEIsQ0FBb0MsU0FBVUMsU0FBVixDQUFlQyxTQUFmLENBQXdCLENBQzFELEdBQUlELFNBQUosQ0FBUyxDQUNQM0MsT0FBQSxDLGVBQUEsRSxlQUFBLENBQWlGMkMsU0FBakYsRUFDQSxPQUFRQSxTQUFSLENBRk8sQ0FJVCxHQUFJQyxTQUFKLENBQWEsQ0FDWDVDLE9BQUEsQyxlQUFBLEUsNEVBQUEsQ0FBNEU0QyxTQUE1RSxFQUNBNUMsT0FBQSxDLGVBQUEsRSx3RkFBQSxDQUFxRjRDLFNBQUEsQyxlQUFBLENBQXJGLEVBRUEsSUFBSUMsU0FBQSxDQUFZRCxTQUFBLEMsU0FBQSxFLGVBQUEsQ0FBaEIsQ0FDQSxJQUFJRSxTQUFBLENBQVlGLFNBQUEsQyxlQUFBLEUsTUFBQSxDQUFoQixDQUNBLElBQUlHLFNBQUEsQ0FBU04sU0FBQSxDLGVBQUEsQ0FBYixDQUVBLElBQUlPLFNBQUEsQ0FBVUosU0FBQSxDLFNBQUEsQ0FBZCxDQUNBSSxTQUFBLEMsTUFBQSxFQUFlRixTQUFmLENBQ0FFLFNBQUEsQyxlQUFBLEUsZUFBQSxDQUNBQSxTQUFBLEMsVUFBQSxFQUFtQlYsU0FBbkIsQ0FDQVUsU0FBQSxDLGVBQUEsRUFBaUJULFNBQWpCLENBQ0FTLFNBQUEsQyxnQkFBQSxFQUF5QnRCLFNBQXpCLENBQ0FzQixTQUFBLEMsZUFBQSxFQUE0QmhDLFNBQUEsQyxNQUFBLENBQTVCLENBTUEsSUFBSWlDLFNBQUEsQ0FBTyxDLFNBQ1QsQ0FBU0QsU0FEQSxDQUFYLENBS0FmLG9CQUFBLENBQXFCUyxTQUFyQixDQUFnQ08sU0FBaEMsQyxlQUFBLEVBR0FmLFFBQUEsQ0FBU1IsU0FBVCxDQUF5QmdCLFNBQXpCLENBQW9DMUIsU0FBcEMsQ0FBMkMrQixTQUEzQyxDQUFtRC9CLFNBQUEsQyxNQUFBLENBQW5ELENBQStEOEIsU0FBL0QsQ0FBMEVELFNBQTFFLEVBNUJXLENBTDZDLENBQTVELEVBTG1CLENBSG1FLENBQTFGLEUsZUFBQSxFQStDUyxTQUFVSyxTQUFWLENBQWUsQ0FDdEJsRCxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBMEVrRCxTQUExRSxFQURzQixDQS9DeEIsRUFOOEQsQ0FBaEUsS0EwREssR0FBSWxDLFNBQUEsQyxjQUFBLEUsZUFBQSxFLGVBQUEsSSxxQkFBSixDQUFnRSxDQUNuRW1DLE9BQUEsQyxlQUFBLEUsZUFBQSxFQUNBQyxnQ0FBQSxDQUFpQzFCLFNBQWpDLENBQWlEVixTQUFqRCxDQUF3REEsU0FBQSxDLE1BQUEsQ0FBeEQsRUFGbUUsQ0FyRnZCLENBa0doRCxHQUFJQSxTQUFBLEMsTUFBQSxJLCtCQUFKLENBQW9ELENBQ2xEaEIsT0FBQSxDLGVBQUEsRSxvRUFBQSxFQUNBQSxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBcURnQixTQUFyRCxFQUNBLElBQUlVLFNBQUEsQ0FBaUJWLFNBQUEsQyxjQUFBLEUsUUFBQSxFLElBQUEsQ0FBckIsQ0FDQWhCLE9BQUEsQyxNQUFBLEUsc0NBQUEsQ0FBeUMwQixTQUF6QyxFQUVBLElBQUkyQixTQUFBLENBQWtCbEQsTUFBQSxDLE1BQUEsRUFBWWEsU0FBQSxDLGNBQUEsRSxlQUFBLEUsYUFBQSxDQUFaLEUsZUFBQSxFLGNBQUEsQ0FBdEIsQ0FDQWhCLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUF5Q3FELFNBQXpDLEVBRUFiLHlDQUFBLENBQTBDZCxTQUExQyxFLGVBQUEsRUFBK0QsU0FBVTRCLFNBQVYsQ0FBK0IsQ0FPNUZ0RCxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBbUZzRCxTQUFuRixFQUNBLEdBQUlBLFNBQUosQ0FBeUIsQ0FDdkIsSUFBSUMsU0FBQSxDQUFZRCxTQUFBLEMsZUFBQSxDQUFoQixDQUNBdEQsT0FBQSxDLGVBQUEsRSxlQUFBLENBQStFdUQsU0FBL0UsRUFFQSxJQUFJQyxTQUFBLENBQVNGLFNBQUEsQyxlQUFBLENBQWIsQ0FDQXRELE9BQUEsQyxNQUFBLEUsZUFBQSxDQUE0RXdELFNBQTVFLEVBTHVCLENBV3pCLElBQUlDLFNBQUEsQ0FBTyxDLFNBQ1QsQ0FBUyxDLGdCQUNQLENBQWdCL0IsU0FEVCxDLE1BRVAsQyxlQUZPLEMsTUFHUCxDLGVBSE8sQyxRQUlQLEMsR0FKTyxDLG1CQUtQLENBQW1CVixTQUFBLEMsZUFBQSxDQUxaLEMsUUFNUCxDQUFRcUMsU0FORCxDQURBLENBQVgsQ0FXQXJELE9BQUEsQyxNQUFBLEUsZUFBQSxDQUFpR2dCLFNBQUEsQyxjQUFBLEUsUUFBQSxFLGVBQUEsQ0FBakcsRUFDQWhCLE9BQUEsQyxNQUFBLEUsZUFBQSxDQUFpRXlELFNBQUEsQyxlQUFBLENBQWpFLEVBRUF4QixvQkFBQSxDQUFxQnNCLFNBQXJCLENBQWdDRSxTQUFoQyxDLGVBQUEsRUFDQXZCLFFBQUEsQ0FBU1IsU0FBVCxDQUF5QjZCLFNBQXpCLENBQW9DdkMsU0FBQSxDLGNBQUEsRSxlQUFBLENBQXBDLENBQXVEd0MsU0FBdkQsQ0FBK0R4QyxTQUFBLEMsTUFBQSxDQUEvRCxDQUEyRU0sU0FBM0UsQyxHQUFBLEVBbEM0RixDQUE5RixFLGVBQUEsRUFvQ1MsU0FBVW9DLFNBQVYsQ0FBZSxDQUN0QjFELE9BQUEsQyxPQUFBLEUsZUFBQSxDQUF3RDBELFNBQXhELEVBRHNCLENBcEN4QixFQVRrRCxDQW1EcEQ1QyxTQUFBLEMsZUFBQSxFQUFjLEMsVUFBRSxDLElBQUYsQ0FBZCxFQWxTaUcsQ0FBbkcsRUFzU0EsU0FBUzBCLHlDQUFULENBQW1EbUIsU0FBbkQsQ0FBbUUsQ0FDakUsT0FBTyxJQUFJQyxPQUFKLENBQVksU0FBVUMsU0FBVixDQUFtQkMsU0FBbkIsQ0FBMkIsQ0FDNUM1RCxtQkFBQSxDLGVBQUEsRUFBNEIsQyxpQkFBRSxDQUFpQnlELFNBQW5CLEMsY0FBbUMsQyw0QkFBbkMsQ0FBNUIsQ0FBNkcsU0FBVUksU0FBVixDQUFlQyxTQUFmLENBQW9DLENBQy9JLEdBQUlELFNBQUosQ0FBU0QsU0FBQSxDQUFPQyxTQUFQLEVBRVQsSUFBSUUsU0FBQSxDQUF1QkQsU0FBM0IsQ0FDQUgsU0FBQSxDQUFRSSxTQUFSLEVBSitJLENBQWpKLEVBRDRDLENBQXZDLENBQVAsQ0FEaUUsQ0FTbEUsQ0FFRCxTQUFTdEMsa0JBQVQsQ0FBNEJ1QyxTQUE1QixDQUE0QyxDQUMxQyxPQUFPLElBQUlOLE9BQUosQ0FBWSxTQUFVTyxTQUFWLENBQW1CQyxTQUFuQixDQUEyQixDQUM1QyxNQUFNQyxTQUFBLENBQVV2RSxPQUFBLEMsZUFBQSxFQUFrQmEsWUFBbEIsQ0FBaEIsQ0FDQTBELFNBQUEsQyxlQUFBLEUsZUFBQSxFQUErQkgsU0FBL0IsQ0FBK0MsU0FBVUksU0FBVixDQUFlQyxTQUFmLENBQTZCLENBQzFFLEdBQUlELFNBQUosQ0FBU0YsU0FBQSxDQUFPRSxTQUFQLEVBRVQsSUFBSUMsU0FBQSxDQUFlQSxTQUFuQixDQUNBSixTQUFBLENBQVFJLFNBQVIsRUFKMEUsQ0FBNUUsRUFGNEMsQ0FBdkMsQ0FBUCxDQUQwQyxDQVUzQyxDQUlELFNBQVN0QyxvQkFBVCxDQUE4QnVDLFNBQTlCLENBQTBDQyxTQUExQyxDQUFnREMsU0FBaEQsQ0FBMEQsQ0FDeER6RSxPQUFBLEMsZUFBQSxFQUEwQnVFLFNBQTFCLENBQXNDQyxTQUF0QyxDQUE0QyxDLEtBQUUsQyxJQUFGLEMsUUFBYSxDLElBQWIsQ0FBNUMsQ0FBeUUsU0FBVUUsU0FBVixDQUFlQyxTQUFmLENBQStCLENBQ3RHLEdBQUlELFNBQUosQ0FBUyxDQUNQM0UsT0FBQSxDLGVBQUEsRSxtQ0FBQSxDQUE2QzJFLFNBQTdDLEVBRE8sQ0FBVCxJQUVPLENBQ0wzRSxPQUFBLEMsZUFBQSxFQUFjNEUsU0FBZCxFQURLLENBSCtGLENBQXhHLEVBRHdELENBVTFELFNBQVMxQyxRQUFULENBQWtCMkMsU0FBbEIsQ0FBa0NDLFNBQWxDLENBQTZDQyxTQUE3QyxDQUFrREMsU0FBbEQsQ0FBMERDLFNBQTFELENBQXdFQyxTQUF4RSxDQUFtRkMsU0FBbkYsQ0FBOEYsQ0FDNUZuRixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBbUNrRixTQUFuQyxFQUNBbEYsT0FBQSxDLE1BQUEsRSxlQUFBLENBQW1DbUYsU0FBbkMsRUFFQSxJQUFJQyxTQUFBLENBQXlCLElBQUlsRixtQkFBSixDQUF3QixDLEtBQ25ELENBQUssSUFBSUUsUUFBQSxDLGlCQUFBLEUsaUJBQUEsQ0FBSixFQUQ4QyxDLGlCQUVuRCxDQUFpQnlFLFNBRmtDLEMsWUFHbkQsQ0FBWUMsU0FIdUMsQyxTQUluRCxDQUFTRSxTQUowQyxDLGNBS25ELENBQWNDLFNBTHFDLEMsV0FNbkQsQ0FBV0MsU0FOd0MsQyxRQU9uRCxDQUFRQyxTQVAyQyxDLFFBUW5ELENBQVFKLFNBUjJDLENBQXhCLENBQTdCLENBV0FLLFNBQUEsQyxNQUFBLEVBQTRCLFNBQVVDLFNBQVYsQ0FBZUMsU0FBZixDQUF5QyxDQUNuRSxHQUFJRCxTQUFKLENBQVMsQ0FDUHJGLE9BQUEsQyxlQUFBLEUsdUJBQUEsQ0FBOEJxRixTQUE5QixFQUNBLE9BQU9FLEdBQUEsQyxlQUFBLEUsS0FBQSxFLE1BQUEsRUFBcUIsQyxTQUFFLEMsR0FBRixDLEtBQWtCLEMsZUFBbEIsQ0FBckIsQ0FBUCxDQUZPLENBSVR2RixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBMENzRixTQUExQyxFQUxtRSxDQUFyRSxFQWY0RixDQXdCOUYsU0FBU2xDLGdDQUFULENBQTBDb0MsU0FBMUMsQ0FBMERDLFNBQTFELENBQStEQyxTQUEvRCxDQUE2RSxDQUMzRXZDLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUErRHFDLFNBQS9ELEVBQ0FyQyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBNkR1QyxTQUE3RCxFQUVBLElBQUlDLFNBQUEsQ0FBeUIsSUFBSXpGLG1CQUFKLENBQXdCLEMsS0FDbkQsQ0FBSyxJQUFJRSxRQUFBLEMsaUJBQUEsRSxVQUFBLENBQUosRUFEOEMsQyxpQkFFbkQsQ0FBaUJvRixTQUZrQyxDLGNBR25ELENBQWNFLFNBSHFDLEMsUUFJbkQsQ0FBUUQsU0FKMkMsQ0FBeEIsQ0FBN0IsQ0FPQUUsU0FBQSxDLGVBQUEsRUFBNEIsU0FBVUMsU0FBVixDQUFlQyxTQUFmLENBQXlDLENBQ25FLEdBQUlELFNBQUosQ0FBUyxDQUNQNUYsT0FBQSxDLE9BQUEsRSx1QkFBQSxDQUE4QjRGLFNBQTlCLEVBQ0EsT0FBT0wsR0FBQSxDLGVBQUEsRSxLQUFBLEUsZUFBQSxFQUFxQixDLFNBQUUsQyxHQUFGLEMsS0FBa0IsQyxlQUFsQixDQUFyQixDQUFQLENBRk8sQ0FJVHBDLE9BQUEsQyxLQUFBLEUsZUFBQSxDQUE0QzBDLFNBQTVDLEVBTG1FLENBQXJFLEVBWDJFLENBb0I3RTlGLE1BQUEsQyxlQUFBLEUsZUFBQSxDQUFrQyxDQUFDTSxRQUFBLEMsZUFBQSxFQUFzQixDLGVBQUEsQyxlQUFBLENBQXRCLENBQXdDLEMsU0FBRSxDLEdBQUYsQ0FBeEMsQ0FBRCxDQUE4REMsVUFBOUQsQ0FBbEMsQ0FBNkcsU0FBVXdGLFNBQVYsQ0FBZUMsU0FBZixDQUFvQixDQUMvSCxJQUFJQyxTQUFBLENBQVlGLFNBQUEsQyxlQUFBLEUsZUFBQSxDQUFoQixDQUNBLElBQUlHLFNBQUEsQ0FBU0gsU0FBQSxDLGVBQUEsRSxlQUFBLENBQWIsQ0FDQTlGLE9BQUEsQyxlQUFBLEUsY0FBQSxDQUFxRGdHLFNBQXJELEVBQ0FoRyxPQUFBLEMsTUFBQSxFLGVBQUEsQ0FBa0RpRyxTQUFsRCxFQUVBaEcsT0FBQSxDLGVBQUEsRUFBZ0IsQyxLQUFFLENBQUsrRixTQUFQLENBQWhCLENBQW9DLFNBQVVFLFNBQVYsQ0FBZUMsU0FBZixDQUF3QixDQUMxRCxHQUFJRCxTQUFKLENBQVMsQ0FDUGxHLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFnRWtHLFNBQWhFLEVBQ0EsT0FBUUEsU0FBUixDQUZPLENBSVQsR0FBSUMsU0FBSixDQUFhLENBQ1huRyxPQUFBLEMsZUFBQSxFLGtEQUFBLENBQWtEbUcsU0FBbEQsRUFFQSxJQUFJQyxTQUFBLENBQWlCRCxTQUFBLEMsZUFBQSxFLGVBQUEsQ0FBckIsQ0FFQSxNQUFNRSxTQUFBLENBQVN2RyxPQUFBLEMsZUFBQSxFQUFrQmEsWUFBbEIsQ0FBZixDQUVBMEYsU0FBQSxDLGVBQUEsRSxlQUFBLEVBQXlCRCxTQUF6QixDQUF5QyxTQUFVRSxTQUFWLENBQWVDLFNBQWYsQ0FBNkIsQ0FFcEUsR0FBSUQsU0FBSixDQUFTLENBQ1B0RyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBK0NzRyxTQUEvQyxFQUNBLE9BQU9QLFNBQUEsQyxRQUFBLEUsS0FBQSxFLGVBQUEsRUFBcUIsQyxTQUFFLEMsR0FBRixDLEtBQWtCLENBQUtPLFNBQXZCLENBQXJCLENBQVAsQ0FGTyxDQUlUdEcsT0FBQSxDLGVBQUEsRSxlQUFBLENBQXNEdUcsU0FBdEQsRUFDQVIsU0FBQSxDLGVBQUEsRUFBU1EsU0FBVCxFQVBvRSxDQUF0RSxFQVBXLENBTDZDLENBQTVELEVBTitILENBQWpJLEVBK0JBeEcsTUFBQSxDLGVBQUEsRSxlQUFBLENBQWtDLENBQUNNLFFBQUEsQyxlQUFBLEVBQXNCLEMsT0FBQSxDLGVBQUEsQ0FBdEIsQ0FBd0MsQyxTQUFFLEMsR0FBRixDQUF4QyxDQUFELENBQThEQyxVQUE5RCxDQUFsQyxDQUE2RyxTQUFVa0csU0FBVixDQUFlQyxTQUFmLENBQW9CLENBRS9ILElBQUlDLFNBQUEsQ0FBWUYsU0FBQSxDLGVBQUEsRSxlQUFBLENBQWhCLENBQ0EsSUFBSUcsU0FBQSxDQUFTSCxTQUFBLEMsZUFBQSxFLGVBQUEsQ0FBYixDQUNBLElBQUlJLFNBQUEsQ0FBUUosU0FBQSxDLGVBQUEsRSxPQUFBLENBQVosQ0FDQXhHLE9BQUEsQyxNQUFBLEUsZUFBQSxDQUFxRDBHLFNBQXJELEVBQ0ExRyxPQUFBLEMsZUFBQSxFLDRDQUFBLENBQWtEMkcsU0FBbEQsRUFDQTNHLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFpRDRHLFNBQWpELEVBQ0EsTUFBTUMsU0FBQSxDQUFTL0csT0FBQSxDLGVBQUEsRUFBa0JhLFlBQWxCLENBQWYsQ0FFQVYsT0FBQSxDLGVBQUEsRUFBZ0IsQyxLQUFFLENBQUt5RyxTQUFQLENBQWhCLENBQW9DLFNBQVVJLFNBQVYsQ0FBZUMsU0FBZixDQUF3QixDQUMxRCxHQUFJRCxTQUFKLENBQVMsQ0FDUDlHLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFnRThHLFNBQWhFLEVBQ0EsT0FBUUEsU0FBUixDQUZPLENBSVQsR0FBSUMsU0FBSixDQUFhLENBQ1gvRyxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBa0QrRyxTQUFsRCxFQUVBLElBQUlDLFNBQUEsQ0FBaUJELFNBQUEsQyxTQUFBLEUsZUFBQSxDQUFyQixDQUdBRixTQUFBLEMsZUFBQSxFLGVBQUEsRUFDRUcsU0FERixFQU5XLENBTDZDLENBQTVELEVBVitILENBQWpJLEVBZ0RBakgsTUFBQSxDLGVBQUEsRSxrQkFBQSxDQUErQixDQUFDTSxRQUFBLEMsZUFBQSxFQUFzQixDLGVBQUEsQyxlQUFBLENBQXRCLENBQXdDLEMsU0FBRSxDLEdBQUYsQ0FBeEMsQ0FBRCxDQUE4REMsVUFBOUQsQ0FBL0IsQ0FBMEcsU0FBVTJHLFNBQVYsQ0FBZUMsU0FBZixDQUFvQixDQUU1SGhILG1CQUFBLEMsZUFBQSxFQUF5QixDLGlCQUFFLENBQWlCK0csU0FBQSxDLGVBQUEsRSxlQUFBLENBQW5CLENBQXpCLEUsZUFBQSxFQUE4RSxDLGdCQUFFLEMsS0FBRixDQUE5RSxFLGVBQUEsRUFBZ0gsU0FBVUUsU0FBVixDQUFlQyxTQUFmLENBQXFDLENBQ25KLEdBQUlELFNBQUosQ0FBUyxDQUNQbkgsT0FBQSxDLE9BQUEsRSxlQUFBLENBQXdEcUgsT0FBeEQsRUFDQSxPQUFPSCxTQUFBLEMsZUFBQSxFLEtBQUEsRSxlQUFBLEVBQXFCLEMsU0FBRSxDLEdBQUYsQyxLQUFrQixDQUFLQyxTQUF2QixDQUFyQixDQUFQLENBRk8sQ0FJVEQsU0FBQSxDLGVBQUEsRUFBU0UsU0FBVCxFQUxtSixDQUFySixFQUY0SCxDQUE5SCxFQWFBckgsTUFBQSxDLGVBQUEsRSxlQUFBLENBQTBDLENBQUNNLFFBQUEsQyxlQUFBLEVBQXNCLEMsZUFBQSxDLEtBQUEsQ0FBdEIsQ0FBd0MsQyxTQUFFLEMsR0FBRixDQUF4QyxDQUFELENBQThEQyxVQUE5RCxDQUExQyxDQUFxSCxTQUFVZ0gsU0FBVixDQUFlQyxTQUFmLENBQW9CLENBRXZJdkgsT0FBQSxDLE1BQUEsRSxlQUFBLENBQXdFc0gsU0FBQSxDLGVBQUEsRSxlQUFBLENBQXhFLEVBQ0EsSUFBSUUsU0FBQSxDQUFTMUgsT0FBQSxDLGVBQUEsRUFBa0JhLFlBQWxCLENBQWIsQ0FFQTZHLFNBQUEsQyxlQUFBLEUsZUFBQSxFQUNFRixTQUFBLEMsZUFBQSxFLGVBQUEsQ0FERixDQUVFLFNBQVVHLFNBQVYsQ0FBZUMsU0FBZixDQUE2QixDQUMzQixHQUFJRCxTQUFKLENBQVMsQ0FDUHpILE9BQUEsQyxlQUFBLEUsZUFBQSxDQUErRHlILFNBQS9ELEVBQ0EsT0FBT0YsU0FBQSxDLFFBQUEsRSxLQUFBLEUsZUFBQSxFQUFxQixDLFNBQUUsQyxHQUFGLEMsS0FBa0IsQ0FBS0UsU0FBdkIsQ0FBckIsQ0FBUCxDQUZPLENBSVR6SCxPQUFBLEMsZUFBQSxFLGtFQUFBLENBQXlEMEgsU0FBekQsRUFDQUgsU0FBQSxDLGVBQUEsRUFBU0csU0FBVCxFQU4yQixDQUYvQixFQUx1SSxDQUF6SSxFQW1CQTNILE1BQUEsQyxlQUFBLEUsZUFBQSxDQUEwQyxDQUFDTSxRQUFBLEMsZUFBQSxFQUFzQixDLE9BQUEsQyxLQUFBLENBQXRCLENBQXdDLEMsU0FBRSxDLEdBQUYsQ0FBeEMsQ0FBRCxDQUE4REMsVUFBOUQsQ0FBMUMsQ0FBcUgsU0FBVXFILFNBQVYsQ0FBZUMsU0FBZixDQUFvQixDQUV2STVILE9BQUEsQyxlQUFBLEUsZUFBQSxDQUF1RDJILFNBQUEsQyxlQUFBLEUsZUFBQSxDQUF2RCxFQUNBLElBQUlFLFNBQUEsQ0FBUy9ILE9BQUEsQyxlQUFBLEVBQWtCYSxZQUFsQixDQUFiLENBRUFrSCxTQUFBLEMsZUFBQSxFLGVBQUEsRSxlQUFBLEVBQ0VGLFNBQUEsQyxlQUFBLEUsV0FBQSxDQURGLENBRUUsU0FBVUcsU0FBVixDQUFlQyxTQUFmLENBQXdCLENBQ3RCLEdBQUlELFNBQUosQ0FBUyxDQUNQOUgsT0FBQSxDLGVBQUEsRSxnRkFBQSxDQUFpRThILFNBQWpFLEVBQ0EsT0FBT0YsU0FBQSxDLGVBQUEsRSxLQUFBLEUsZUFBQSxFQUFxQixDLFNBQUUsQyxHQUFGLEMsS0FBa0IsQ0FBS0UsU0FBdkIsQ0FBckIsQ0FBUCxDQUZPLENBSVQ5SCxPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBNEQrSCxTQUE1RCxFQUNBSCxTQUFBLEMsZUFBQSxFQUFTRyxTQUFULEVBTnNCLENBRjFCLEVBTHVJLENBQXpJLEVBbUJBaEksTUFBQSxDLGVBQUEsRSxjQUFBLENBQW1DLENBQUNNLFFBQUEsQyxjQUFBLEVBQXNCLEMsZUFBQSxDLGVBQUEsQ0FBdEIsQ0FBd0MsQyxTQUFFLEMsR0FBRixDQUF4QyxDQUFELENBQThEQyxVQUE5RCxDQUFuQyxDQUE4RyxTQUFVMEgsU0FBVixDQUFlQyxRQUFmLENBQW9CLENBQ2hJakksT0FBQSxDLE9BQUEsRSxlQUFBLENBQStDZ0ksU0FBQSxDLFFBQUEsQ0FBL0MsRUFDQSxJQUFJRSxTQUFBLENBQVlGLFNBQUEsQyxlQUFBLEUsV0FBQSxDQUFoQixDQUVBaEksT0FBQSxDLE9BQUEsRSxlQUFBLENBQTBEa0ksU0FBMUQsRUFFQWhJLG1CQUFBLEMsTUFBQSxFQUF5QixDLFlBQUUsQ0FBWWdJLFNBQWQsQyxjQUF5QixDLGVBQXpCLENBQXpCLENBQWdHLGVBQWdCQyxTQUFoQixDQUFxQkMsU0FBckIsQ0FBbUMsQ0FDakksR0FBSUQsU0FBSixDQUFTLENBQ1BuSSxPQUFBLEMsT0FBQSxFLDREQUFBLENBQW9EbUksU0FBcEQsRUFDQSxPQUFRQSxTQUFSLENBRk8sQ0FJVCxHQUFJQyxTQUFKLENBQWtCLENBRWhCQSxTQUFBLEMsR0FBQSxFLGVBQUEsRSxlQUFBLEVBQ0FwSSxPQUFBLEMsT0FBQSxFLGNBQUEsQ0FBeUVvSSxTQUFBLEMsR0FBQSxFLGVBQUEsRSxlQUFBLENBQXpFLEVBQ0EsTUFBTUMsU0FBQSxDQUFhRCxTQUFBLEMsR0FBQSxFLGVBQUEsRSxlQUFBLENBQW5CLENBQ0EsTUFBTUUsU0FBQSxDQUFTeEksT0FBQSxDLGVBQUEsRUFBa0JhLFlBQWxCLENBQWYsQ0FFQSxNQUFNNEgsU0FBQSxDQUFXLE1BQU1ELFNBQUEsQyxjQUFBLEUsVUFBQSxFQUNyQkQsU0FEcUIsQ0FBdkIsQ0FHQXJJLE9BQUEsQyxlQUFBLEUsd0ZBQUEsQ0FBdUV1SSxTQUF2RSxFQUVBLE1BQU1DLFNBQUEsQ0FBaUIsTUFBTUYsU0FBQSxDLGVBQUEsRSxlQUFBLEVBQTJCLEMsVUFDdEQsQ0FBVUQsU0FENEMsQyxNQUV0RCxDLGNBRnNELENBQTNCLENBQTdCLENBSUFFLFNBQUEsQyxnQkFBQSxFQUE2QkMsU0FBN0IsQ0FDQXhJLE9BQUEsQyxPQUFBLEUsZUFBQSxDQUF3RW9JLFNBQXhFLEVBQ0FILFFBQUEsQyxlQUFBLEVBQVNNLFNBQVQsRUFsQmdCLENBTCtHLENBQW5JLEVBTmdJLENBQWxJLEVBcUNBeEksTUFBQSxDLE1BQUEsRSx3QkFBQSxDQUFzQyxDQUFDTSxRQUFBLEMsY0FBQSxFQUFzQixDLGVBQUEsQyxLQUFBLENBQXRCLENBQXdDLEMsU0FBRSxDLEdBQUYsQ0FBeEMsQ0FBRCxDQUE4REMsVUFBOUQsQ0FBdEMsQ0FBaUgsZUFBZ0JtSSxTQUFoQixDQUFxQkMsU0FBckIsQ0FBMEIsQ0FDekksSUFBSUMsU0FBQSxDQUFhRixTQUFBLEMsZUFBQSxFLGVBQUEsQ0FBakIsQ0FDQXpJLE9BQUEsQyxlQUFBLEUsZUFBQSxDQUFvRTJJLFNBQXBFLEVBQ0EzSSxPQUFBLEMsZUFBQSxFLHFFQUFBLENBQTBEeUksU0FBQSxDLGVBQUEsQ0FBMUQsRUFFQSxNQUFNRyxTQUFBLENBQVM5SSxPQUFBLEMsZUFBQSxFQUFrQmEsWUFBbEIsQ0FBZixDQUVBLElBQUlrSSxTQUFKLENBQ0EsR0FBSSxDQUNGQSxTQUFBLENBQWdCLE1BQU1ELFNBQUEsQyxlQUFBLEUsUUFBQSxFQUE2QixDLE1BQ2pELEMsTUFEaUQsQyxNQUVqRCxDQUFNLEMsUUFDSixDQUFRSCxTQUFBLEMsZUFBQSxFLGNBQUEsQ0FESixDLFdBRUosQ0FBV0EsU0FBQSxDLGVBQUEsRSxlQUFBLENBRlAsQyxVQUdKLENBQVVBLFNBQUEsQyxNQUFBLEUsZUFBQSxDQUhOLEMsS0FJSixDQUFLQSxTQUFBLEMsTUFBQSxFLGNBQUEsQ0FKRCxDQUYyQyxDQUE3QixDQUF0QixDQURFLENBVUYsTUFBT0ssU0FBUCxDQUFVLENBQ1Y5SSxPQUFBLEMsZUFBQSxFLDREQUFBLENBQXVEOEksU0FBdkQsRUFDQSxPQUFPSixTQUFBLEMsZUFBQSxFLEtBQUEsRSxlQUFBLEVBQXFCLEMsU0FBRSxDLEdBQUYsQyxLQUFrQixDQUFLSSxTQUF2QixDQUFyQixDQUFQLENBRlUsQ0FLWjlJLE9BQUEsQyxPQUFBLEUsZUFBQSxDQUF3QzZJLFNBQXhDLEVBRUEsR0FBSSxDQUNGLE1BQU1FLFNBQUEsQ0FBaUIsTUFBTUgsU0FBQSxDLGVBQUEsRSxlQUFBLEVBQzNCQyxTQUFBLEMsSUFBQSxDQUQyQixDQUUzQixDLFVBQUUsQ0FBVUYsU0FBWixDQUYyQixDQUE3QixDQUtBM0ksT0FBQSxDLGVBQUEsRSxlQUFBLENBQWtEK0ksU0FBbEQsRUFORSxDQU9GLE1BQU9DLFNBQVAsQ0FBVSxDQUNWaEosT0FBQSxDLGVBQUEsRSw4REFBQSxDQUF5RGdKLFNBQXpELEVBQ0EsT0FBT04sU0FBQSxDLGVBQUEsRSxLQUFBLEUsZUFBQSxFQUFxQixDLFNBQUUsQyxHQUFGLEMsS0FBa0IsQ0FBS00sU0FBdkIsQ0FBckIsQ0FBUCxDQUZVLENBS1osTUFBTUMsU0FBQSxDQUFZLE1BQU1MLFNBQUEsQyxjQUFBLEUsZUFBQSxFQUN0QkQsU0FEc0IsQ0FFdEIsQyxrQkFDRSxDQUFrQixDLHdCQUFFLENBQXdCRSxTQUFBLEMsSUFBQSxDQUExQixDQURwQixDQUZzQixDQUF4QixDQUtBSCxTQUFBLEMsZUFBQSxFQUFTTyxTQUFULEVBMUN5SSxDQUEzSSxFQThDQWxKLE1BQUEsQyxlQUFBLEUsZUFBQSxDQUEyQyxDQUFDTSxRQUFBLEMsZUFBQSxFQUFzQixDLE9BQUEsQyxlQUFBLENBQXRCLENBQXdDLEMsU0FBRSxDLEdBQUYsQ0FBeEMsQ0FBRCxDQUE4REMsVUFBOUQsQ0FBM0MsQ0FBc0gsZUFBZ0I0SSxTQUFoQixDQUFxQkMsU0FBckIsQ0FBMEIsQ0FDOUluSixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBbURrSixTQUFBLEMsZUFBQSxDQUFuRCxFQUNBLElBQUlFLFNBQUEsQ0FBY0YsU0FBQSxDLGVBQUEsRSxlQUFBLENBQWxCLENBQ0FsSixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBbUVvSixTQUFuRSxFQUNBLE1BQU1DLFNBQUEsQ0FBU3ZKLE9BQUEsQyxlQUFBLEVBQWtCYSxZQUFsQixDQUFmLENBRUEsTUFBTTJJLFNBQUEsQ0FBVyxNQUFNRCxTQUFBLEMsV0FBQSxFLGVBQUEsRUFDckJELFNBRHFCLENBQXZCLENBSUEsTUFBTUcsU0FBQSxDQUE0QkQsU0FBQSxDLGNBQUEsRSxlQUFBLENBQWxDLENBQ0EsSUFBSUUsU0FBSixDQUNBLEdBQUksQ0FDRkEsU0FBQSxDQUFpQixNQUFNSCxTQUFBLEMsZ0JBQUEsRSxlQUFBLEVBQTJCLEMsVUFDaEQsQ0FBVUQsU0FEc0MsQyxNQUVoRCxDLGNBRmdELENBQTNCLENBQXZCLENBSUFwSixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBMkR3SixTQUEzRCxFQUxFLENBTUYsTUFBT0MsU0FBUCxDQUFVLENBQ1YsT0FBT04sU0FBQSxDLGVBQUEsRSxLQUFBLEUsTUFBQSxFQUFxQixDLFNBQUUsQyxHQUFGLEMsS0FBa0IsQ0FBS00sU0FBdkIsQ0FBckIsQ0FBUCxDQURVLENBSVp6SixPQUFBLEMsZUFBQSxFLGVBQUEsQ0FBdUZ1SixTQUF2RixFQUNBQyxTQUFBLEMsY0FBQSxFLGVBQUEsRUFBNEJFLFNBQUEsRUFBaUIsQ0FDM0MsR0FBSUEsU0FBQSxDLElBQUEsSUFBcUJILFNBQXpCLENBQW9ELENBQ2xEdkosT0FBQSxDLGVBQUEsRSxzRkFBQSxDQUFpRjBKLFNBQUEsQyxJQUFBLENBQWpGLEVBQ0FDLGlCQUFBLENBQWtCRCxTQUFBLEMsSUFBQSxDQUFsQixDQUFvQyxTQUFVRSxTQUFWLENBQWtCLENBQ3BENUosT0FBQSxDLGVBQUEsRSxlQUFBLENBQTBDNEosU0FBMUMsRUFEb0QsQ0FBdEQsRUFGa0QsQ0FEVCxDQUE3QyxFQVFBVCxTQUFBLEMsZUFBQSxFQUFTSyxTQUFULEVBL0I4SSxDQUFoSixFQWtDQSxlQUFlRyxpQkFBZixDQUFpQ0UsU0FBakMsQ0FBa0RDLFNBQWxELENBQTRELENBQzFEOUosT0FBQSxDLGVBQUEsRSxlQUFBLENBQXVENkosU0FBdkQsRUFDQSxNQUFNRSxTQUFBLENBQVNqSyxPQUFBLEMsZUFBQSxFQUFrQmEsWUFBbEIsQ0FBZixDQUNBLElBQUlxSixTQUFKLENBQ0EsR0FBSSxDQUNGQSxTQUFBLENBQWdCLE1BQU1ELFNBQUEsQyxlQUFBLEUsZUFBQSxFQUNwQkYsU0FEb0IsQ0FBdEIsQ0FHQUMsU0FBQSxDQUFTRSxTQUFULEVBSkUsQ0FLRixNQUFPQyxTQUFQLENBQVUsQ0FDVkgsU0FBQSxDQUFTRyxTQUFULEVBRFUsQ0FUOEMsQ0FpRDVEQyxNQUFBLEMsZUFBQSxFQUFpQm5LLE1BQWpCIiwic291cmNlc0NvbnRlbnQiOlsidmFyIGV4cHJlc3MgPSByZXF1aXJlKCdleHByZXNzJyk7XG52YXIgcm91dGVyID0gZXhwcmVzcy5Sb3V0ZXIoKTtcbi8vIHZhciB3aW5zdG9uID0gcmVxdWlyZSgnLi4vLi4vLi4vLi4vY29uZmlnL3dpbnN0b24nKTtcbnZhciB3aW5zdG9uID0gcmVxdWlyZSgnLi4vLi4vLi4vY29uZmlnL3dpbnN0b24nKVxuLy8gdmFyIFByb2plY3QgPSByZXF1aXJlKFwiLi4vLi4vLi4vLi4vbW9kZWxzL3Byb2plY3RcIik7XG52YXIgUHJvamVjdCA9IHJlcXVpcmUoXCIuLi8uLi8uLi9tb2RlbHMvcHJvamVjdFwiKTtcbnZhciBTdWJzY3JpcHRpb25QYXltZW50ID0gcmVxdWlyZShcIi4uL21vZGVscy9zdWJzY3JpcHRpb24tcGF5bWVudFwiKTtcbnZhciBtb21lbnQgPSByZXF1aXJlKCdtb21lbnQnKTtcbnZhciBtb25nb29zZSA9IHJlcXVpcmUoJ21vbmdvb3NlJyk7XG52YXIgcGFzc3BvcnQgPSByZXF1aXJlKCdwYXNzcG9ydCcpO1xuLy8gcmVxdWlyZSgnLi4vLi4vLi4vLi4vbWlkZGxld2FyZS9wYXNzcG9ydCcpKHBhc3Nwb3J0KTtcbnJlcXVpcmUoXCIuLi8uLi8uLi9taWRkbGV3YXJlL3Bhc3Nwb3J0XCIpKHBhc3Nwb3J0KTtcbi8vIHZhciB2YWxpZHRva2VuID0gcmVxdWlyZSgnLi4vLi4vLi4vLi4vbWlkZGxld2FyZS92YWxpZC10b2tlbicpXG52YXIgdmFsaWR0b2tlbiA9IHJlcXVpcmUoJy4uLy4uLy4uL21pZGRsZXdhcmUvdmFsaWQtdG9rZW4nKVxuLy8gU2V0IHlvdXIgc2VjcmV0IGtleTogcmVtZW1iZXIgdG8gY2hhbmdlIHRoaXMgdG8geW91ciBsaXZlIHNlY3JldCBrZXkgaW4gcHJvZHVjdGlvblxuLy8gU2VlIHlvdXIga2V5cyBoZXJlOiBodHRwczovL2Rhc2hib2FyZC5zdHJpcGUuY29tL2FjY291bnQvYXBpa2V5c1xuY29uc3QgYXBpS2V5ID0gcHJvY2Vzcy5lbnYuUEFZTUVOVF9TVFJJUEVfQVBJS0VZO1xud2luc3Rvbi5kZWJ1Zygnc3RyaXBlIGFwaUtleScgKyBhcGlLZXkpO1xuXG5jb25zdCBzdHJpcGUgPSByZXF1aXJlKCdzdHJpcGUnKShhcGlLZXkpO1xuXG4vLyBGaW5kIHlvdXIgZW5kcG9pbnQncyBzZWNyZXQgaW4geW91ciBEYXNoYm9hcmQncyB3ZWJob29rIHNldHRpbmdzXG5jb25zdCBlbmRwb2ludFNlY3JldCA9IHByb2Nlc3MuZW52LlBBWU1FTlRfU1RSSVBFX1NFQ1JFVDtcbndpbnN0b24uZGVidWcoJ3N0cmlwZSBlbmRwb2ludFNlY3JldCcgKyBlbmRwb2ludFNlY3JldCk7XG5cbmNvbnN0IGFwaVNlY3JldEtleSA9IHByb2Nlc3MuZW52LlBBWU1FTlRfU1RSSVBFX0FQSV9TRUNSRVRfS0VZO1xud2luc3Rvbi5kZWJ1Zygnc3RyaXBlIGFwaVNlY3JldEtleScgKyBhcGlTZWNyZXRLZXkpO1xuXG5jb25zdCBib2R5UGFyc2VyID0gcmVxdWlyZSgnYm9keS1wYXJzZXInKTtcblxuLy8gaHR0cHM6Ly9zdHJpcGUuY29tL2RvY3MvcGF5bWVudHMvY2hlY2tvdXQvZnVsZmlsbG1lbnQjd2ViaG9va3Ncbi8vIGN1cmwgLVggUE9TVCAtdSBhbmRyZWEubGVvQGYyMS5pdDoxMjM0NTYgLUggJ0NvbnRlbnQtVHlwZTphcHBsaWNhdGlvbi9qc29uJyAtZCAne1widHlwZVwiOlwicGF5bWVudF9pbnRlbnQuc3VjY2VlZGVkXCJ9JyAgaHR0cDovL2xvY2FsaG9zdDozMDAwL21vZHVsZXMvcGF5bWVudHMvc3RyaXBlL3dlYmhvb2tcbnJvdXRlci5wb3N0KCcvd2ViaG9vaycsIGJvZHlQYXJzZXIucmF3KHsgdHlwZTogJ2FwcGxpY2F0aW9uL2pzb24nIH0pLCBmdW5jdGlvbiAocmVxdWVzdCwgcmVzcG9uc2UpIHtcbiAgXG4gIHdpbnN0b24uZGVidWcoJ8K7wrvCu8K7IHN0cmlwZSBlbmRwb2ludFNlY3JldDogJyArIGVuZHBvaW50U2VjcmV0KVxuICB3aW5zdG9uLmRlYnVnKCfCu8K7wrvCuyBzdHJpcGUgYXBpS2V5OiAnICsgYXBpS2V5KTtcbiAgd2luc3Rvbi5kZWJ1Zygnc3RyaXBlIGFwaVNlY3JldEtleScgKyBhcGlTZWNyZXRLZXkpO1xuXG4gIGNvbnN0IHNpZyA9IHJlcXVlc3QuaGVhZGVyc1snc3RyaXBlLXNpZ25hdHVyZSddO1xuXG4gIHdpbnN0b24uZGVidWcoJ3N0cmlwZSBzaWc6ICcsIHNpZylcblxuICBsZXQgZXZlbnQ7XG5cbiAgdHJ5IHtcbiAgICBldmVudCA9IHN0cmlwZS53ZWJob29rcy5jb25zdHJ1Y3RFdmVudChyZXF1ZXN0LnJhd0JvZHksIHNpZywgZW5kcG9pbnRTZWNyZXQpO1xuICB9IGNhdGNoIChlcnIpIHtcbiAgICB3aW5zdG9uLmVycm9yKCcqKioqIFN0cmlwZSBlcnJvciBjb25zdHJ1Y3RFdmVudDogJywgZXJyLm1lc3NhZ2UpXG4gICAgcmV0dXJuIHJlc3BvbnNlLnN0YXR1cyg0MDApLnNlbmQoYFdlYmhvb2sgRXJyb3I6ICR7ZXJyLm1lc3NhZ2V9YCk7XG4gIH1cblxuICAvKipcbiAgICoqISAqKiogSGFuZGxlIHRoZSBjaGVja291dC5zZXNzaW9uLmNvbXBsZXRlZCBldmVudCAqKipcbiAgICogTk9URTogVEhJUyBFVkVOVCBPQ0NVUiBPTkxZIFdIRU4gSVMgQ1JFQVRFRCBBIE5FVyBTVUJTQ1JJUFRJT05cbiAgICogMSkgRlJPTSBUSEUgUFJPUEVSVFkgXCJjbGllbnRfcmVmZXJlbmNlX2lkXCIgQVJFIE9CVEFJTkVEIFRIRTpcbiAgICogICAgQSkgVVNFUiBJRFxuICAgKiAgICBCKSBQUk9KRUNUIElEIFxuICAgKiAyKSBXSVRIIFRIRSBzdWJzY3JpcHRpb24gSUQgKE5PVEUgaXMgdGhlIHByb3BlcnR5IHN1YnNjcmlwdGlvbikgSVMgUlVOTkVEIEEgQ0FMTEJBQ0sgVE8gU1RSSVBFXG4gICAqICAgIFRPIE9CVEFJTiBUSEUgT0JKRUNUIFwiU1VCU0NSSVBUSU9OXCIgQU5EIFRIRU4gRlJPTSBUSElTIEFSRSBPQlRBSU5FRCBUSEU6XG4gICAqICAgIEEpIHN1YnNjcmlwdGlvblN0YXJ0RGF0ZVxuICAgKiAgICBCKSBzdWJzY3JpcHRpb25FbmREYXRlXG4gICAqICAgIEMpIHF1YW50aXR5ICh3aGljaCBpbiB0aGUgdGlsZWRlc2sgZGFzaGJvYXJkIGNvcnJlc3BvbmRzIHRvIHRoZSBudW1iZXIgb2YgYWdlbnRzIC8gb3BlcmF0b3JzIHNlYXRzKVxuICAgKiAzKSBXSVRIIFRIRSBPQlRBSU5FRCBEQVRBIElTIFVQREFURUQgVEhFIFBST0pFQ1QgJ1BST0ZJTEUnIChpcyBhbiBvYmplY3QgbmVzdGVkIGluIHRoZSBwcm9qZWN0IG9iamVjdCkgXG4gICAqIDQpIFRIRSBPQlRBSU5FRCBPQkpFQ1QgXCJTVUJTQ1JJUFRJT05cIiBBTkQgdGhlIFwiU1VCU0NSSVBUSU9OIElEXCIsIHRoZSBcIlBST0pFQ1QgSURcIiwgdGhlIFwiVVNFUiBJRFwiIEFORCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIiBBUkUgU0FWRUQgT04gT1VSIERCXG4gICAqICAgIEFTIFwiU1VCU0NSSVBUSU9OLVBBWU1FTlRcIlxuICAgKi9cblxuICBpZiAoZXZlbnQudHlwZSA9PT0gJ2NoZWNrb3V0LnNlc3Npb24uY29tcGxldGVkJykge1xuICAgIHdpbnN0b24uZGVidWcoJyEhISEhISEhIEhJICEhISEhISEhIGNoZWNrb3V0LnNlc3Npb24uY29tcGxldGVkJyk7XG5cbiAgICBjb25zdCBzZXNzaW9uID0gZXZlbnQuZGF0YS5vYmplY3Q7XG4gICAgd2luc3Rvbi5pbmZvKCdzdHJpcGUgY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQnLCBzZXNzaW9uKTtcblxuICAgIHZhciBjbGllbnRfcmVmZXJlbmNlX2lkID0gc2Vzc2lvbi5jbGllbnRfcmVmZXJlbmNlX2lkO1xuICAgIHdpbnN0b24uaW5mbygnc3RyaXBlIGNsaWVudF9yZWZlcmVuY2VfaWQnLCBjbGllbnRfcmVmZXJlbmNlX2lkKTtcblxuICAgIC8qKiAxQSAqL1xuICAgIHZhciB1c2VyX2lkID0gY2xpZW50X3JlZmVyZW5jZV9pZC5zcGxpdChcIl9cIilbMF07XG4gICAgd2luc3Rvbi5pbmZvKCdzdHJpcGUgdXNlcl9pZDonICsgdXNlcl9pZCk7XG5cbiAgICAvKiogMUIgKi9cbiAgICB2YXIgcHJvamVjdF9pZCA9IGNsaWVudF9yZWZlcmVuY2VfaWQuc3BsaXQoXCJfXCIpWzFdO1xuICAgIHdpbnN0b24uaW5mbygnc3RyaXBlIHByb2plY3RfaWQ6ICcgKyBwcm9qZWN0X2lkKTtcblxuICAgIHZhciBwbGFuX25hbWUgPSBjbGllbnRfcmVmZXJlbmNlX2lkLnNwbGl0KFwiX1wiKVsyXTtcbiAgICB3aW5zdG9uLmluZm8oJ3N0cmlwZSBwbGFuX25hbWU6ICcgKyBwbGFuX25hbWUpO1xuXG4gICAgdmFyIHNlYXRzX251bUFzU3RyaW5nID0gY2xpZW50X3JlZmVyZW5jZV9pZC5zcGxpdChcIl9cIilbM107XG4gICAgd2luc3Rvbi5pbmZvKCdzdHJpcGUgc2VhdHNfbnVtOiAnICsgc2VhdHNfbnVtQXNTdHJpbmcgKyAndHlwZW9mICcsIHR5cGVvZiBzZWF0c19udW1Bc1N0cmluZyk7XG5cbiAgICB2YXIgc2VhdHNfbnVtID0gTnVtYmVyKHNlYXRzX251bUFzU3RyaW5nKVxuICAgIHdpbnN0b24uaW5mbygnc3RyaXBlIHNlYXRzX251bUFzU3RyaW5nOiAnICsgc2VhdHNfbnVtICsgJyB0eXBlb2YgJywgdHlwZW9mIHNlYXRzX251bSk7XG5cbiAgICB2YXIgc3Vic2NyaXB0aW9uSWQgPSBldmVudC5kYXRhLm9iamVjdC5zdWJzY3JpcHRpb247XG4gICAgd2luc3Rvbi5pbmZvKCcqKiogKioqICEhISEhISEhISEhISEhISEhISEhISEgY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQgLSBzdWJzY3JpcHRpb24gSUQ6ICcsIHN1YnNjcmlwdGlvbklkKTtcblxuICAgIGdldFN1YnNjcml0aW9uQnlJZChzdWJzY3JpcHRpb25JZCkudGhlbihmdW5jdGlvbiAoc3Vic2NyaXB0aW9uX29iaikge1xuXG4gICAgICB2YXIgb2JqZWN0X3R5cGUgPSBzdWJzY3JpcHRpb25fb2JqLm9iamVjdDtcbiAgICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBjaGVja291dC5zZXNzaW9uLmNvbXBsZXRlZCAtIGdldFN1YnNjcml0aW9uQnlJZCBzdWJzY3Igb2JqZWN0X3R5cGU6ICcsIG9iamVjdF90eXBlKTtcblxuICAgICAgLyoqIDJBICovXG4gICAgICB2YXIgc3Vic2NyaXB0aW9uU3RhcnREYXRlID0gbW9tZW50LnVuaXgoc3Vic2NyaXB0aW9uX29iai5jdXJyZW50X3BlcmlvZF9zdGFydCkuZm9ybWF0KCdZWVlZLU1NLUREVEhIOm1tOnNzLlNTUycpXG4gICAgICB3aW5zdG9uLmluZm8oJyoqKiAqKiogY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQgLSBnZXRTdWJzY3JpdGlvbkJ5SWQgKioqIHN0YXJ0ICoqKiA6ICcsIHN1YnNjcmlwdGlvblN0YXJ0RGF0ZSk7XG5cbiAgICAgIC8qKiAyQiAqL1xuICAgICAgdmFyIHN1YnNjcmlwdGlvbkVuZERhdGUgPSBtb21lbnQudW5peChzdWJzY3JpcHRpb25fb2JqLmN1cnJlbnRfcGVyaW9kX2VuZCkuZm9ybWF0KCdZWVlZLU1NLUREVEhIOm1tOnNzLlNTUycpXG4gICAgICB3aW5zdG9uLmluZm8oJyoqKiAqKiogY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQgLSBnZXRTdWJzY3JpcHRpb24gKioqIGVuZCAqKiogOiAnLCBzdWJzY3JpcHRpb25FbmREYXRlKTtcblxuICAgICAgLyoqXG4gICAgICAgKiEgKioqIEJPRFkgKioqXG4gICAgICAgKi9cbiAgICAgIHZhciBib2R5ID0ge1xuICAgICAgICBwcm9maWxlOiB7XG4gICAgICAgICAgbmFtZTogcGxhbl9uYW1lLFxuICAgICAgICAgIHR5cGU6ICdwYXltZW50JyxcbiAgICAgICAgICBzdWJzY3JpcHRpb25fY3JlYXRpb25fZGF0ZTogc3Vic2NyaXB0aW9uU3RhcnREYXRlLFxuICAgICAgICAgIHN1YlN0YXJ0OiBzdWJzY3JpcHRpb25TdGFydERhdGUsXG4gICAgICAgICAgc3ViRW5kOiBzdWJzY3JpcHRpb25FbmREYXRlLFxuICAgICAgICAgIHN1YnNjcmlwdGlvbklkOiBzdWJzY3JpcHRpb25JZCxcbiAgICAgICAgICBsYXN0X3N0cmlwZV9ldmVudDogZXZlbnQudHlwZSxcbiAgICAgICAgICBhZ2VudHM6IHNlYXRzX251bVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8qKiAzICovXG4gICAgICB1cGRhdGVQcm9qZWN0UHJvZmlsZShwcm9qZWN0X2lkLCBib2R5LCAnY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQnKTtcblxuICAgICAgLyoqIDQgKi9cbiAgICAgIHNhdmVPbkRCKHN1YnNjcmlwdGlvbklkLCBwcm9qZWN0X2lkLCBzdWJzY3JpcHRpb25fb2JqLCB1c2VyX2lkLCBldmVudC50eXBlLCBwbGFuX25hbWUsIHNlYXRzX251bSlcblxuICAgIH0pLmNhdGNoKGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgIHdpbnN0b24uZXJyb3IoJyoqKiAqKiogY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWQgLSBnZXRTdWJzY3JpdGlvbkJ5SWQgZXJyICcsIGVycik7XG4gICAgfSk7XG5cbiAgfVxuICAvLyBvYmplY3RfdHlwZSA9IHN1YnNjcmlwdGlvbi5cbiAgLyoqXG4gICAqKiEgKioqIEhhbmRsZSB0aGUgaW52b2ljZS5wYXltZW50X3N1Y2NlZWRlZCBldmVudCAqKipcbiAgICogTk9URTogVEhJUyBFVkVOVCBPQ0NVUiBXSEVOIElTIENSRUFURUQgQSBORVcgU1VCU0NSSVBUSU9OIEFORCBFVkVSWSBUSU1FIFRIRSBTQU1FIElTIFJFTkVXRURcbiAgICogMSkgRlJPTSBUSEUgT0JKRUNUIE9GIFRIRSBFVkVOVCBBUkUgT0JUQUlORUQgVEhFOlxuICAgKiAgICBBKSBzdWJzY3JpcHRpb25TdGFydERhdGVcbiAgICogICAgQikgc3Vic2NyaXB0aW9uRW5kRGF0ZVxuICAgKiAgICBDKSBzdWJzY3JpdHB0aW9uSWQgIFxuICAgKiAyKSB3aGVuIHJlZmVyIHRvIGEgUkVORVdBTCAoIGJpbGxpbmdfcmVhc29uID09PSBzdWJzY3JpcHRpb25fY3ljbGUpXG4gICAqICAgIElGIFRIRSBWQUxVRSBPRiBUSEUgUFJPUEVSVFkgXCJiaWxsaW5nX3JlYXNvblwiIElTIFVOTElLRSBPRiBcInN1YnNjcmlwdGlvbl9jcmVhdGVcIiAoTUVBTlMgVEhBVCBUSEUgRVZFTlQgUkVGRVIgVE8gQSBSRU5FV0FMIFxuICAgKiAgICBGT1IgV0hJQ0ggYmlsbGluZ19yZWFzb246IHN1YnNjcmlwdGlvbl9jeWNsZSBBTkQgTk9UIFRPIFRIRSBGSVJTVCBTVUJTQ1JJUFRJT04pIFxuICAgKiAgICBBKSBJUyBPQlRBSU5FRCBUSEUgJ1NVQlNDUklQVElPTi1QQVlNRU5UJyAod2l0aCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIikgU0FWRUQgQUJPVkUgQU5EIFRIRU4gRlJPTSBUSElTOlxuICAgKiAgICAgICAtIFRIRSBcIlBST0pFQ1QgSURcIiBcbiAgICogICAgICAgLSBUSEUgXCJVU0VSIElEXCJcbiAgICogICAgICAgVE8gT0JUQUlOIFRIRSAnU1VCU0NSSVBUSU9OLVBBWU1FTlQnSU4gT1VSIERCIElTIExPT0tFRCBGT1IgVEhFIFwiU1VCU0NSSVBJT04tUEFZTUVOVFwiIFRIQVQgSEFTIEZPUiBzdWJzY3JpcHRpb24gaWQgVEhFIFZBTFVFIE9GIDFDIEFORCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIlxuICAgKiAgICAgICBUSEUgQ09ORElUSU9OIHN0cmlwZV9ldmVudDogXCJjaGVja291dC5zZXNzaW9uLmNvbXBsZXRlZFwiIChOTyBNT1JFOiBvYmplY3RfdHlwZSA9IHN1YnNjcmlwdGlvbikgSVQnUyBORUNFU1NBUlkgVE8gRklMVEVSIFRIRSBcIlNVQlNDUklQSU9OLVBBWU1FTlRTXCIgXG4gICAqICAgICAgIFRIQVQgQ09OVEFJTiBUSEUgIFBST1BFUlRJRVMgIFwicHJvamVjdElEXCIgQU5EIFwidXNlcklEXCJcbiAgICogICAgICAgVEhFIEVYSVNURU5DRSBPRiBBIFwiU1VCU0NSSVBUSU9OLVBBWU1FTlRcIiBXSVRIT1VUIFwicHJvamVjdElEXCIgQU5EIFwidXNlcklEXCIgSVMgRFVFIFRPIFRIRSBGQUNUIFRIQVQgQVQgVEhFIE1PTUVOVCBPRiBUSEUgQ1JFQVRJT04gT0YgQSBTVUJTQ1JJUFRJT04gXG4gICAqICAgICAgIElTIFNBVkVEIElOIE9VUiBEQiBBTFNPIEEgXCJTVUJTQ1JJUFRJT04tUEFZTUVOVFwiIENPUlJFU1BPTkRJTkcgVE8gVEhFIE9CSkVDVCBSRVRVUk5FRCBCWSAgVEhFIEVWRU5UIFwiaW52b2ljZS5wYXltZW50X3N1Y2NlZWRlZFwiIFVTRUZVTCBCRUNBVVNFIFxuICAgKiAgICAgICBJVCBDT05UQUlOUyBUSEUgTElOS1MgT0YgVEhFIElOVk9JQ0UgT0YgVEhFIEZJUlNUIFNVQlNDUklQVElPTiAodXNlZCBieSB0aWxlZGVzayBkYXNoYm9hcmQgaW4gdGhlIGNvbXBvbmVudHMgcGF5bWVudHMpXG4gICAqICAgICAgIEJVVCBGUk9NIFdISUNIIElUIElTIE5PVCBQT1NTSUJMRSBQVVNIIEFORCBUSEVOIFRPIFRPIE9CVEFJTiBUSEUgXCJwcm9qZWN0SURcIiBBTkQgXCJ1c2VySURcIlxuICAgKiAgICBCKSBJUyBVUERBVEVEIFRIRSBQUk9KRUNUICdQUk9GSUxFJyBXSVRIOlxuICAgKiAgICAgICAtIERBVEEgT0JUQUlORUQgRlJPTSBUSEUgRVZFTlQnUyBPQkpFQ1Q6XG4gICAqICAgICAgICAgLSBzdWJzY3JpcHRpb25TdGFydERhdGVcbiAgICogICAgICAgICAtIHN1YnNjcmlwdGlvbkVuZERhdGVcbiAgICogICAgICAgICAtIHN1YnNjcmlwdGlvbklkIFxuICAgKiAgICAgICAgIC0gbGFzdF9zdHJpcGVfZXZlbnQgXG4gICAqICAgICAgICAgLSBxdWFudGl0eSAod2hpY2ggaW4gdGhlIGRhc2hib2FyZCBjb3JyZXNwb25kcyB0byB0aGUgbnVtYmVyIG9mIGFnZW50cyAvIG9wZXJhdG9ycyBzZWF0cylcbiAgICogICAgICAgLSBEQVRBIE9CVEFJTkVEIEZST00gVEhFICdTVUJTQ1JJUFRJT04tUEFZTUVOVCcgd2l0aCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIlxuICAgKiAgICAgICAgIC0gXCJQUk9KRUNUIElEXCIgYW5kIFwiVVNFUiBJRFwiXG4gICAqICAgIEMpIFRIRSBPQkpFQ1QgT0YgVEhFIEVWRU5UIGludm9pY2UucGF5bWVudF9zdWNjZWVkZWQgQU5EIHRoZSBcIlNVQlNDUklQVElPTiBJRFwiLCB0aGUgXCJQUk9KRUNUIElEXCIsIHRoZSBcIlVTRVIgSURcIiBBTkQgdGhlIFwiT0JKRUNUX1RZUEVcIiAoZXZlbnQpIFxuICAgKiAgICAgICBBUkUgU0FWRUQgQVMgXCJTVUJTQ1JJUFRJT04tUEFZTUVOVFwiIE9OIE9VUiBEQlxuICAgKiAzKSB3aGVuIHJlZmVyIHRvIGEgQ1JFQVRJT04gKGJpbGxpbmdfcmVhc29uID09PSBzdWJzY3JpcHRpb25fY3JlYXRlKVxuICAgKiAgICBBKSBUSEUgT0JKRUNUIE9GIFRIRSBFVkVOVCBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkIEFORCBUSEUgREFUQSBldmVudC50eXBlID09PSBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkICBBUkUgU0FWRUQgQVMgXCJTVUJTQ1JJUFRJT04tUEFZTUVOVFwiXG4gICAqICAgICAgIE9OIE9VUiBEQiAoTk9URTogXCJQUk9KRUNUIElEXCIgQU5EIFwiVVNFUiBJRFwiIEFSRSBTQVZFRCBBUyBudWxsIFNJTkNFIElTIE5PVCBQT1NTSUJMRSBUTyBSRVRSSUVWRSBUSEUgVkFMVUVTKSBcbiAgICogXG4gICAqL1xuICBpZiAoZXZlbnQudHlwZSA9PT0gJ2ludm9pY2UucGF5bWVudF9zdWNjZWVkZWQnKSB7XG5cbiAgICB3aW5zdG9uLmluZm8oJyAhISEhISEhISBISSAhISEhISEhISBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkJyk7XG4gICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGludm9pY2UucGF5bWVudF9zdWNjZWVkZWQgLSBCSUxMSU5HIFJFQVNPTiAnLCBldmVudC5kYXRhLm9iamVjdC5iaWxsaW5nX3JlYXNvbik7XG5cbiAgICB2YXIgbGluZXNOdW0gPSBldmVudC5kYXRhLm9iamVjdC5saW5lcy50b3RhbF9jb3VudFxuICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkIC0gbGluZXNOdW06ICcsIGxpbmVzTnVtKTtcblxuICAgIHZhciBpbmRleCA9IGxpbmVzTnVtIC0gMTtcbiAgICB3aW5zdG9uLmluZm8oJyoqKiAqKiogaW52b2ljZS5wYXltZW50X3N1Y2NlZWRlZCAtIGluZGV4OiAnLCBpbmRleCk7XG5cbiAgICAvKiogMUEgKi8gLy8gVEhJUyBXT1JLU1xuICAgIHZhciBzdWJzY3JpcHRpb25TdGFydERhdGUgPSBtb21lbnQudW5peChldmVudC5kYXRhLm9iamVjdC5saW5lcy5kYXRhW2luZGV4XS5wZXJpb2Quc3RhcnQpLmZvcm1hdCgnWVlZWS1NTS1ERFRISDptbTpzcy5TU1MnKVxuICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkIC0gc3RhcnQ6ICcsIHN1YnNjcmlwdGlvblN0YXJ0RGF0ZSk7XG5cbiAgICAvKiogMUIgKi8gLy8gVEhJUyBXT1JLU1xuICAgIHZhciBzdWJzY3JpcHRpb25FbmREYXRlID0gbW9tZW50LnVuaXgoZXZlbnQuZGF0YS5vYmplY3QubGluZXMuZGF0YVtpbmRleF0ucGVyaW9kLmVuZCkuZm9ybWF0KCdZWVlZLU1NLUREVEhIOm1tOnNzLlNTUycpXG4gICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGludm9pY2UucGF5bWVudF9zdWNjZWVkZWQgLSBlbmQ6ICcsIHN1YnNjcmlwdGlvbkVuZERhdGUpO1xuXG4gICAgdmFyIHN1YnNjcmlwdGlvbklkID0gZXZlbnQuZGF0YS5vYmplY3Quc3Vic2NyaXB0aW9uO1xuICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkIC0gc3Vic2NyaXB0aW9uIElEOiAnLCBzdWJzY3JpcHRpb25JZCk7XG5cbiAgICBpZiAoc3Vic2NyaXB0aW9uSWQgPT0gbnVsbCkge1xuICAgICAgc3Vic2NyaXB0aW9uSWQgPSBldmVudC5kYXRhLm9iamVjdC5saW5lcy5kYXRhWzBdLnN1YnNjcmlwdGlvbjtcbiAgICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkIC0gc3Vic2NyaXB0aW9uIElEOiAnLCBzdWJzY3JpcHRpb25JZCk7XG4gICAgfVxuXG4gICAgaWYgKGV2ZW50LmRhdGEub2JqZWN0LmJpbGxpbmdfcmVhc29uICE9PSAnc3Vic2NyaXB0aW9uX2NyZWF0ZScpIHtcblxuXG4gICAgICAvLyBUTyBURVNUIFRIRSBSRU5FV0FMIFVOQ09NTUVOVCBTRVQgVElNRU9VVFxuICAgICAgLy8gc2V0VGltZW91dChmdW5jdGlvbiAoKSB7XG4gICAgICAvKiogMkEgKi9cbiAgICAgIGdldFN1YkJ5SWRBbmRDaGVja291dFNlc3Npb25Db21wbGV0ZWRFdm50KHN1YnNjcmlwdGlvbklkKS50aGVuKGZ1bmN0aW9uIChzdWJzcHRuX3BheW1lbnQpIHtcbiAgICAgICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGdldFN1YkJ5SWRBbmRDaGVja291dFNlc3Npb25Db21wbGV0ZWRFdm50IHN1YnNwdG5fcGF5bWVudDogJywgc3Vic3B0bl9wYXltZW50KTtcbiAgICAgICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGdldFN1YkJ5SWRBbmRDaGVja291dFNlc3Npb25Db21wbGV0ZWRFdm50IHN1YnNwdG5fcGF5bWVudCB0eXBlb2Ygc3Vic3B0bl9wYXltZW50OiAnLCB0eXBlb2Ygc3Vic3B0bl9wYXltZW50KTtcbiAgICAgICAgaWYgKHN1YnNwdG5fcGF5bWVudCkge1xuXG4gICAgICAgICAgdmFyIHByb2plY3RJZCA9IHN1YnNwdG5fcGF5bWVudC5wcm9qZWN0X2lkXG4gICAgICAgICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGdldFN1YkJ5SWRBbmRDaGVja291dFNlc3Npb25Db21wbGV0ZWRFdm50IHN1YnNwdG5fcGF5bWVudCA+IHByb2plY3RfaWQ6ICcsIHByb2plY3RJZCk7XG5cbiAgICAgICAgICBQcm9qZWN0LmZpbmRPbmUoeyBfaWQ6IHByb2plY3RJZCB9LCBmdW5jdGlvbiAoZXJyLCBwcm9qZWN0KSB7XG4gICAgICAgICAgICBpZiAoZXJyKSB7XG4gICAgICAgICAgICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBnZXRTdWJCeUlkQW5kQ2hlY2tvdXRTZXNzaW9uQ29tcGxldGVkRXZudCAgZmluZCBQcm9qZWN0ICcsIGVycik7XG4gICAgICAgICAgICAgIHJldHVybiAoZXJyKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChwcm9qZWN0KSB7XG4gICAgICAgICAgICAgIHdpbnN0b24uaW5mbygnKioqICoqKiBnZXRTdWJCeUlkQW5kQ2hlY2tvdXRTZXNzaW9uQ29tcGxldGVkRXZudCAgcHJvamVjdCAnLCBwcm9qZWN0KTtcbiAgICAgICAgICAgICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIGdldFN1YkJ5SWRBbmRDaGVja291dFNlc3Npb25Db21wbGV0ZWRFdm50ICBwcm9qZWN0ID4gcHJvZmlsZScsIHByb2plY3QucHJvZmlsZSk7XG5cbiAgICAgICAgICAgICAgdmFyIHNlYXRzX251bSA9IHByb2plY3QucHJvZmlsZS5hZ2VudHM7XG4gICAgICAgICAgICAgIHZhciBwbGFuX25hbWUgPSBwcm9qZWN0LnByb2ZpbGUubmFtZTtcbiAgICAgICAgICAgICAgdmFyIHVzZXJJZCA9IHN1YnNwdG5fcGF5bWVudC51c2VyX2lkXG5cbiAgICAgICAgICAgICAgbGV0IHByb2ZpbGUgPSBwcm9qZWN0LnByb2ZpbGU7XG4gICAgICAgICAgICAgIHByb2ZpbGUubmFtZSA9IHBsYW5fbmFtZTtcbiAgICAgICAgICAgICAgcHJvZmlsZS50eXBlID0gXCJwYXltZW50XCI7XG4gICAgICAgICAgICAgIHByb2ZpbGUuc3ViU3RhcnQgPSBzdWJzY3JpcHRpb25TdGFydERhdGU7XG4gICAgICAgICAgICAgIHByb2ZpbGUuc3ViRW5kID0gc3Vic2NyaXB0aW9uRW5kRGF0ZTtcbiAgICAgICAgICAgICAgcHJvZmlsZS5zdWJzY3JpcHRpb25JZCA9IHN1YnNjcmlwdGlvbklkO1xuICAgICAgICAgICAgICBwcm9maWxlLmxhc3Rfc3RyaXBlX2V2ZW50ID0gZXZlbnQudHlwZTtcblxuXG4gICAgICAgICAgICAgIC8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gICAgICAgICAgICAgIC8vICBCT0RZIFxuICAgICAgICAgICAgICAvLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICAgICAgICAgICAgICB2YXIgYm9keSA9IHtcbiAgICAgICAgICAgICAgICBwcm9maWxlOiBwcm9maWxlXG4gICAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgICAvKiogMkIgKi9cbiAgICAgICAgICAgICAgdXBkYXRlUHJvamVjdFByb2ZpbGUocHJvamVjdElkLCBib2R5LCAnaW52b2ljZS5wYXltZW50X3N1Y2NlZWRlZCcpO1xuICAgICAgICAgICAgICBcbiAgICAgICAgICAgICAgLyoqIDJDICovXG4gICAgICAgICAgICAgIHNhdmVPbkRCKHN1YnNjcmlwdGlvbklkLCBwcm9qZWN0SWQsIGV2ZW50LCB1c2VySWQsIGV2ZW50LnR5cGUsIHBsYW5fbmFtZSwgc2VhdHNfbnVtKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG5cbiAgICAgIH0pLmNhdGNoKGZ1bmN0aW9uIChlcnIpIHtcbiAgICAgICAgd2luc3Rvbi5lcnJvcignKioqICoqKiBnZXRTdWJCeUlkQW5kQ2hlY2tvdXRTZXNzaW9uQ29tcGxldGVkRXZudCAtIGVyciAnLCBlcnIpO1xuICAgICAgfSk7XG4gICAgICAvLyB9LCA1MDAwKTtcbiAgICB9IFxuICAgIGVsc2UgaWYgKGV2ZW50LmRhdGEub2JqZWN0LmJpbGxpbmdfcmVhc29uID09PSAnc3Vic2NyaXB0aW9uX2NyZWF0ZScpIHtcbiAgICAgIGNvbnNvbGUubG9nKCdVU0VDQVNFICBpbnZvaWNlLnBheW1lbnRfc3VjY2VlZGVkICBiaWxsaW5nX3JlYXNvbiBzdWJzY3JpcHRpb25fY3JlYXRlICcpIFxuICAgICAgc2F2ZUZpcnN0SW52b2ljZVBheW1lbnRTdWNjZWVkZWQoc3Vic2NyaXB0aW9uSWQsIGV2ZW50LCBldmVudC50eXBlKTtcbiAgICB9IFxuICBcbiAgfVxuXG4gIC8qKlxuICAgKiohICoqKiBIQU5ETEUgVEhFIEVWRU5UIGN1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkICoqKlxuICAgKiB0aGlzIGV2ZW50IG9jY3VycyB3aGVuIGEgc3Vic2NyaXB0aW9uIGlzIGNhbmNlbGxlZFxuICAgKiAtIGZyb20gdGhlIFRJTEVERVNLJyBkYXNoYm9hcmQgKGJ5IGNsaWNraW5nIENBTkNFTCBTVUJTQ1JJUFRJT04gdGhhdCBjYWxsIGNhbmNlbHN1YnNjcmlwdGlvbigpIC0gc2VlIGJlbG93KVxuICAgKiAtIGZyb20gdGhlIFNUUklQRScgZGFzaGJvYXJkXG4gICAqL1xuICBpZiAoZXZlbnQudHlwZSA9PT0gJ2N1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkJykge1xuICAgIHdpbnN0b24uaW5mbygnICEhISEhISEhIEhJICEhISEhISEhISEhIGN1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkJyk7XG4gICAgd2luc3Rvbi5pbmZvKCdjdXN0b21lci5zdWJzY3JpcHRpb24uZGVsZXRlZCBldmVudCAnLCBldmVudCk7XG4gICAgdmFyIHN1YnNjcmlwdGlvbklkID0gZXZlbnQuZGF0YS5vYmplY3QuaWRcbiAgICB3aW5zdG9uLmluZm8oJyoqKiAqKiogc3Vic2NyaXB0aW9uIElEICcsIHN1YnNjcmlwdGlvbklkKTtcblxuICAgIHZhciBzdWJzY3JpcHRpb25FbmQgPSBtb21lbnQudW5peChldmVudC5kYXRhLm9iamVjdC5jYW5jZWxlZF9hdCkuZm9ybWF0KCdZWVlZLU1NLUREVEhIOm1tOnNzLlNTUycpXG4gICAgd2luc3Rvbi5pbmZvKCcqKiogKioqIHN1YnNjcmlwdGlvbkVuZCAnLCBzdWJzY3JpcHRpb25FbmQpO1xuXG4gICAgZ2V0U3ViQnlJZEFuZENoZWNrb3V0U2Vzc2lvbkNvbXBsZXRlZEV2bnQoc3Vic2NyaXB0aW9uSWQpLnRoZW4oZnVuY3Rpb24gKHN1YnNjcmlwdGlvblBheW1lbnQpIHtcbiAgICAgIC8vIFN1YnNjcmlwdGlvblBheW1lbnQuZmluZCh7IHN1YnNjcmlwdGlvbl9pZDogc3Vic2NyaXB0aW9uSWQsIG9iamVjdF90eXBlOiBcInN1YnNjcmlwdGlvblwiIH0sIGZ1bmN0aW9uIChlcnIsIHN1YnNjcmlwdGlvblBheW1lbnQpIHtcblxuICAgICAgLy8gaWYgKGVycikge1xuICAgICAgLy8gICB3aW5zdG9uLmluZm8oJ0Vycm9yIGdldHRpbmcgdGhlIHN1YnNjcmlwdGlvblBheW1lbnQgJywgZXJyKTtcbiAgICAgIC8vICAgcmV0dXJuIGVycjtcbiAgICAgIC8vIH1cbiAgICAgIHdpbnN0b24uaW5mbygnKioqICoqKiDCu8K7wrsgwrvCu8K7IGN1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkIHN1YnNjcmlwdGlvblBheW1lbnQgJywgc3Vic2NyaXB0aW9uUGF5bWVudCk7XG4gICAgICBpZiAoc3Vic2NyaXB0aW9uUGF5bWVudCkgeyBcbiAgICAgICAgdmFyIHByb2plY3RJZCA9IHN1YnNjcmlwdGlvblBheW1lbnQucHJvamVjdF9pZDsgXG4gICAgICAgIHdpbnN0b24uaW5mbygnY3VzdG9tZXIuc3Vic2NyaXB0aW9uLmRlbGV0ZWQgc3Vic2NyaXB0aW9uUGF5bWVudCBwcm9qZWN0IGlkOiAnLCBwcm9qZWN0SWQpXG4gICAgICAgIFxuICAgICAgICB2YXIgdXNlcklkID0gc3Vic2NyaXB0aW9uUGF5bWVudC51c2VyX2lkO1xuICAgICAgICB3aW5zdG9uLmluZm8oJ2N1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkIHN1YnNjcmlwdGlvblBheW1lbnQgdXNlciBpZDogJywgdXNlcklkKVxuICAgICAgfVxuXG4gICAgICAvKipcbiAgICAgICAqISAqKiogQk9EWSAqKipcbiAgICAgICAqL1xuICAgICAgdmFyIGJvZHkgPSB7XG4gICAgICAgIHByb2ZpbGU6IHtcbiAgICAgICAgICBzdWJzY3JpcHRpb25JZDogc3Vic2NyaXB0aW9uSWQsXG4gICAgICAgICAgbmFtZTogJ1NhbmRib3gnLFxuICAgICAgICAgIHR5cGU6ICdmcmVlJyxcbiAgICAgICAgICBhZ2VudHM6IDEsXG4gICAgICAgICAgbGFzdF9zdHJpcGVfZXZlbnQ6IGV2ZW50LnR5cGUsXG4gICAgICAgICAgc3ViRW5kOiBzdWJzY3JpcHRpb25FbmRcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICB3aW5zdG9uLmluZm8oXCJjdXN0b21lci5zdWJzY3JpcHRpb24uZGVsZXRlZCBwcm9maWxlIHRvIHVwZGF0ZSAoZXZlbnQuZGF0YS5vYmplY3QuY2FuY2VsZWRfYXQpIFwiLCBldmVudC5kYXRhLm9iamVjdC5jYW5jZWxlZF9hdClcbiAgICAgIHdpbnN0b24uaW5mbyhcImN1c3RvbWVyLnN1YnNjcmlwdGlvbi5kZWxldGVkIHByb2ZpbGUgdG8gdXBkYXRlIFwiLCBib2R5LnByb2ZpbGUpXG4gICAgICBcbiAgICAgIHVwZGF0ZVByb2plY3RQcm9maWxlKHByb2plY3RJZCwgYm9keSwgJ3N1YnNjcmlwdGlvbi5kZWxldGVkJyk7XG4gICAgICBzYXZlT25EQihzdWJzY3JpcHRpb25JZCwgcHJvamVjdElkLCBldmVudC5kYXRhLm9iamVjdCwgdXNlcklkLCBldmVudC50eXBlLCBwbGFuX25hbWUsIDEpO1xuXG4gICAgfSkuY2F0Y2goZnVuY3Rpb24gKGVycikge1xuICAgICAgd2luc3Rvbi5lcnJvcignKioqICoqKiBjdXN0b21lci5zdWJzY3JpcHRpb24uZGVsZXRlZCAnLCBlcnIpO1xuICAgIH0pO1xuXG4gIH1cbiAgLy8gUmV0dXJuIGEgcmVzcG9uc2UgdG8gYWNrbm93bGVkZ2UgcmVjZWlwdCBvZiB0aGUgZXZlbnRcbiAgcmVzcG9uc2UuanNvbih7IHJlY2VpdmVkOiB0cnVlIH0pO1xufSk7XG5cblxuZnVuY3Rpb24gZ2V0U3ViQnlJZEFuZENoZWNrb3V0U2Vzc2lvbkNvbXBsZXRlZEV2bnQoc3Vic2NyaXB0aW9uaWQpIHtcbiAgcmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uIChyZXNvbHZlLCByZWplY3QpIHtcbiAgICBTdWJzY3JpcHRpb25QYXltZW50LmZpbmRPbmUoeyBzdWJzY3JpcHRpb25faWQ6IHN1YnNjcmlwdGlvbmlkLCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIiB9LCBmdW5jdGlvbiAoZXJyLCBzdWJzY3JpcHRpb25QYXltZW50KSB7XG4gICAgICBpZiAoZXJyKSByZWplY3QoZXJyKTtcblxuICAgICAgdmFyIHN1YnNjcmlwdGlvbl9wYXltZW50ID0gc3Vic2NyaXB0aW9uUGF5bWVudDtcbiAgICAgIHJlc29sdmUoc3Vic2NyaXB0aW9uX3BheW1lbnQpO1xuICAgIH0pO1xuICB9KVxufTtcblxuZnVuY3Rpb24gZ2V0U3Vic2NyaXRpb25CeUlkKHN1YnNjcmlwdGlvbmlkKSB7XG4gIHJldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbiAocmVzb2x2ZSwgcmVqZWN0KSB7XG4gICAgY29uc3QgX3N0cmlwZSA9IHJlcXVpcmUoXCJzdHJpcGVcIikoYXBpU2VjcmV0S2V5KTtcbiAgICBfc3RyaXBlLnN1YnNjcmlwdGlvbnMucmV0cmlldmUoc3Vic2NyaXB0aW9uaWQsIGZ1bmN0aW9uIChlcnIsIHN1YnNjcmlwdGlvbikge1xuICAgICAgaWYgKGVycikgcmVqZWN0KGVycik7XG5cbiAgICAgIHZhciBzdWJzY3JpcHRpb24gPSBzdWJzY3JpcHRpb247XG4gICAgICByZXNvbHZlKHN1YnNjcmlwdGlvbik7XG4gICAgfSk7XG4gIH0pXG59O1xuXG4vLyBTRUUgVXBkYXRpbmcgbmVzdGVkIG9iamVjdCBpbiBtb25nb29zZSBcbi8vIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzIzODMyOTIxL3VwZGF0aW5nLW5lc3RlZC1vYmplY3QtaW4tbW9uZ29vc2VcbmZ1bmN0aW9uIHVwZGF0ZVByb2plY3RQcm9maWxlKHByb2plY3RfaWQsIGJvZHksIGNhbGxlZEJ5KSB7XG4gIFByb2plY3QuZmluZEJ5SWRBbmRVcGRhdGUocHJvamVjdF9pZCwgYm9keSwgeyBuZXc6IHRydWUsIHVwc2VydDogdHJ1ZSB9LCBmdW5jdGlvbiAoZXJyLCB1cGRhdGVkUHJvamVjdCkge1xuICAgIGlmIChlcnIpIHtcbiAgICAgIHdpbnN0b24uZXJyb3IoJ3VwZGF0ZVByb2plY3RQcm9maWxlIEVycm9yICcsIGVycik7XG4gICAgfSBlbHNlIHtcbiAgICAgIHdpbnN0b24uZGVidWcodXBkYXRlZFByb2plY3QpXG4gICAgfVxuICB9KTtcbn1cblxuZnVuY3Rpb24gc2F2ZU9uREIoc3Vic2NyaXB0aW9uaWQsIHByb2plY3RpZCwgb2JqLCB1c2VyaWQsIHN0cmlwZV9ldmVudCwgcGxhbl9uYW1lLCBzZWF0c19udW0pIHtcbiAgd2luc3Rvbi5pbmZvKCdzYXZlT25EQiBwbGFuX25hbWUnLCBwbGFuX25hbWUpXG4gIHdpbnN0b24uaW5mbygnc2F2ZU9uREIgc2VhdHNfbnVtJywgc2VhdHNfbnVtKVxuXG4gIHZhciBuZXdQYXltZW50U3Vic2NyaXB0aW9uID0gbmV3IFN1YnNjcmlwdGlvblBheW1lbnQoe1xuICAgIF9pZDogbmV3IG1vbmdvb3NlLlR5cGVzLk9iamVjdElkKCksXG4gICAgc3Vic2NyaXB0aW9uX2lkOiBzdWJzY3JpcHRpb25pZCxcbiAgICBwcm9qZWN0X2lkOiBwcm9qZWN0aWQsXG4gICAgdXNlcl9pZDogdXNlcmlkLFxuICAgIHN0cmlwZV9ldmVudDogc3RyaXBlX2V2ZW50LFxuICAgIHBsYW5fbmFtZTogcGxhbl9uYW1lLFxuICAgIGFnZW50czogc2VhdHNfbnVtLFxuICAgIG9iamVjdDogb2JqXG4gIH0pO1xuXG4gIG5ld1BheW1lbnRTdWJzY3JpcHRpb24uc2F2ZShmdW5jdGlvbiAoZXJyLCBzYXZlZFN1YnNjcmlwdGlvblBheW1lbnQpIHtcbiAgICBpZiAoZXJyKSB7XG4gICAgICB3aW5zdG9uLmVycm9yKCctLS0gPiBFUlJPUiAnLCBlcnIpXG4gICAgICByZXR1cm4gcmVzLnN0YXR1cyg1MDApLnNlbmQoeyBzdWNjZXNzOiBmYWxzZSwgbXNnOiAnRXJyb3Igc2F2aW5nIG9iamVjdC4nIH0pO1xuICAgIH1cbiAgICB3aW5zdG9uLmluZm8oJ3NhdmVkU3Vic2NyaXB0aW9uUGF5bWVudCAnLCBzYXZlZFN1YnNjcmlwdGlvblBheW1lbnQpO1xuICB9KTtcbn1cblxuZnVuY3Rpb24gc2F2ZUZpcnN0SW52b2ljZVBheW1lbnRTdWNjZWVkZWQoc3Vic2NyaXB0aW9uaWQsIG9iaiwgc3RyaXBlX2V2ZW50KSB7XG4gIGNvbnNvbGUubG9nKCdzYXZlRmlyc3RJbnZvaWNlUGF5bWVudFN1Y2NlZWRlZCBzdWJzY3JpcHRpb25pZCcsIHN1YnNjcmlwdGlvbmlkKVxuICBjb25zb2xlLmxvZygnc2F2ZUZpcnN0SW52b2ljZVBheW1lbnRTdWNjZWVkZWQgc3RyaXBlX2V2ZW50Jywgc3RyaXBlX2V2ZW50KVxuXG4gIHZhciBuZXdQYXltZW50U3Vic2NyaXB0aW9uID0gbmV3IFN1YnNjcmlwdGlvblBheW1lbnQoe1xuICAgIF9pZDogbmV3IG1vbmdvb3NlLlR5cGVzLk9iamVjdElkKCksXG4gICAgc3Vic2NyaXB0aW9uX2lkOiBzdWJzY3JpcHRpb25pZCxcbiAgICBzdHJpcGVfZXZlbnQ6IHN0cmlwZV9ldmVudCxcbiAgICBvYmplY3Q6IG9ialxuICB9KTtcblxuICBuZXdQYXltZW50U3Vic2NyaXB0aW9uLnNhdmUoZnVuY3Rpb24gKGVyciwgc2F2ZWRTdWJzY3JpcHRpb25QYXltZW50KSB7XG4gICAgaWYgKGVycikge1xuICAgICAgd2luc3Rvbi5lcnJvcignLS0tID4gRVJST1IgJywgZXJyKVxuICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAwKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogJ0Vycm9yIHNhdmluZyBvYmplY3QuJyB9KTtcbiAgICB9XG4gICAgY29uc29sZS5sb2coJ3NhdmVkU3Vic2NyaXB0aW9uUGF5bWVudCA+PiAnLCBzYXZlZFN1YnNjcmlwdGlvblBheW1lbnQpO1xuICB9KTtcbn1cblxucm91dGVyLnB1dCgnL2NhbmNlbHN1YnNjcmlwdGlvbicsIFtwYXNzcG9ydC5hdXRoZW50aWNhdGUoWydiYXNpYycsICdqd3QnXSwgeyBzZXNzaW9uOiBmYWxzZSB9KSwgdmFsaWR0b2tlbl0sIGZ1bmN0aW9uIChyZXEsIHJlcykge1xuICB2YXIgcHJvamVjdGlkID0gcmVxLmJvZHkucHJvamVjdGlkXG4gIHZhciB1c2VyaWQgPSByZXEuYm9keS51c2VyaWRcbiAgd2luc3Rvbi5pbmZvKCfCu8K7wrsgwrvCu8K7IGNhbmNlbHN1YnNjcmlwdGlvbiBwcm9qZWN0aWQnLCBwcm9qZWN0aWQpO1xuICB3aW5zdG9uLmluZm8oJ8K7wrvCuyDCu8K7wrsgY2FuY2Vsc3Vic2NyaXB0aW9uIHVzZXJpZCcsIHVzZXJpZCk7XG5cbiAgUHJvamVjdC5maW5kT25lKHsgX2lkOiBwcm9qZWN0aWQgfSwgZnVuY3Rpb24gKGVyciwgcHJvamVjdCkge1xuICAgIGlmIChlcnIpIHtcbiAgICAgIHdpbnN0b24uZXJyb3IoJy0tID4gY2FuY2Vsc3Vic2NyaXB0aW9uIEVycm9yIGdldHRpbmcgcHJvamVjdCAnLCBlcnIpXG4gICAgICByZXR1cm4gKGVycik7XG4gICAgfVxuICAgIGlmIChwcm9qZWN0KSB7XG4gICAgICB3aW5zdG9uLmluZm8oJy0tID4gY2FuY2Vsc3Vic2NyaXB0aW9uICBwcm9qZWN0ICcsIHByb2plY3QpO1xuXG4gICAgICB2YXIgc3Vic2NyaXB0aW9uaWQgPSBwcm9qZWN0LnByb2ZpbGUuc3Vic2NyaXB0aW9uSWQ7XG5cbiAgICAgIGNvbnN0IHN0cmlwZSA9IHJlcXVpcmUoXCJzdHJpcGVcIikoYXBpU2VjcmV0S2V5KTtcblxuICAgICAgc3RyaXBlLnN1YnNjcmlwdGlvbnMuZGVsKHN1YnNjcmlwdGlvbmlkLCBmdW5jdGlvbiAoZXJyLCBjb25maXJtYXRpb24pIHtcbiAgICAgICAgLy8gYXN5bmNocm9ub3VzbHkgY2FsbGVkXG4gICAgICAgIGlmIChlcnIpIHtcbiAgICAgICAgICB3aW5zdG9uLmVycm9yKCctLSA+IGNhbmNlbHN1YnNjcmlwdGlvbiAgZXJyICcsIGVycik7XG4gICAgICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAwKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogZXJyIH0pXG4gICAgICAgIH1cbiAgICAgICAgd2luc3Rvbi5pbmZvKCctLSA+IGNhbmNlbHN1YnNjcmlwdGlvbiBjb25maXJtYXRpb24gJywgY29uZmlybWF0aW9uKVxuICAgICAgICByZXMuanNvbihjb25maXJtYXRpb24pO1xuICAgICAgfSk7XG4gICAgfVxuICB9KVxufSk7XG5cbnJvdXRlci5wdXQoJy91cGRhdGVzdWJzY3JpcHRpb24nLCBbcGFzc3BvcnQuYXV0aGVudGljYXRlKFsnYmFzaWMnLCAnand0J10sIHsgc2Vzc2lvbjogZmFsc2UgfSksIHZhbGlkdG9rZW5dLCBmdW5jdGlvbiAocmVxLCByZXMpIHtcblxuICB2YXIgcHJvamVjdGlkID0gcmVxLmJvZHkucHJvamVjdGlkXG4gIHZhciB1c2VyaWQgPSByZXEuYm9keS51c2VyaWRcbiAgdmFyIHByaWNlID0gcmVxLmJvZHkucHJpY2VcbiAgd2luc3Rvbi5pbmZvKCfCu8K7wrsgwrvCu8K7IHVwZGF0ZXN1YnNjcmlwdGlvbiBwcm9qZWN0aWQnLCBwcm9qZWN0aWQpO1xuICB3aW5zdG9uLmluZm8oJ8K7wrvCuyDCu8K7wrsgdXBkYXRlc3Vic2NyaXB0aW9uIHVzZXJpZCcsIHVzZXJpZCk7XG4gIHdpbnN0b24uaW5mbygnwrvCu8K7IMK7wrvCuyB1cGRhdGVzdWJzY3JpcHRpb24gcHJpY2UnLCBwcmljZSk7XG4gIGNvbnN0IHN0cmlwZSA9IHJlcXVpcmUoXCJzdHJpcGVcIikoYXBpU2VjcmV0S2V5KTtcblxuICBQcm9qZWN0LmZpbmRPbmUoeyBfaWQ6IHByb2plY3RpZCB9LCBmdW5jdGlvbiAoZXJyLCBwcm9qZWN0KSB7XG4gICAgaWYgKGVycikge1xuICAgICAgd2luc3Rvbi5lcnJvcignLS0gPiB1cGRhdGVzdWJzY3JpcHRpb24gRXJyb3IgZ2V0dGluZyBwcm9qZWN0ICcsIGVycilcbiAgICAgIHJldHVybiAoZXJyKTtcbiAgICB9XG4gICAgaWYgKHByb2plY3QpIHtcbiAgICAgIHdpbnN0b24uaW5mbygnLS0gPiB1cGRhdGVzdWJzY3JpcHRpb24gIHByb2plY3QgJywgcHJvamVjdCk7XG5cbiAgICAgIHZhciBzdWJzY3JpcHRpb25pZCA9IHByb2plY3QucHJvZmlsZS5zdWJzY3JpcHRpb25JZDtcblxuICAgICAgLy8gc3Vic2NyaXB0aW9uaWRcbiAgICAgIHN0cmlwZS5zdWJzY3JpcHRpb25zLnVwZGF0ZShcbiAgICAgICAgc3Vic2NyaXB0aW9uaWQsXG4gICAgICAgIC8vIHsgdHJpYWxfZW5kOiAxNTY4MjEwNjU1IH0sXG4gICAgICAgIC8vIGZ1bmN0aW9uIChlcnIsIHN1YnNjcmlwdGlvbikge1xuICAgICAgICAvLyAgIC8vIGFzeW5jaHJvbm91c2x5IGNhbGxlZFxuICAgICAgICAvLyAgIGlmIChlcnIpIHtcbiAgICAgICAgLy8gICAgIHdpbnN0b24uZXJyb3IoJ3VwZGF0ZXN1YnNjcmlwdGlvbiBzdWJzY3JpcHRpb24gZXJyICcsIGVycilcbiAgICAgICAgLy8gICAgIHJldHVyblxuICAgICAgICAvLyAgIH1cbiAgICAgICAgLy8gICB3aW5zdG9uLmluZm8oJ3VwZGF0ZXN1YnNjcmlwdGlvbiBzdWJzY3JpcHRpb24gJywgc3Vic2NyaXB0aW9uKVxuICAgICAgICAvLyB9XG4gICAgICApO1xuICAgIH1cblxuICAgIC8vIHN0cmlwZS5zdWJzY3JpcHRpb25zLnJldHJpZXZlKHN1YnNjcmlwdGlvbmlkLCBmdW5jdGlvbiAoZXJyLCBzdWJzY3JpcHRpb24pIHtcbiAgICAvLyAgIC8vIGFzeW5jaHJvbm91c2x5IGNhbGxlZFxuICAgIC8vICAgaWYgKGVycikge1xuICAgIC8vICAgICB3aW5zdG9uLmluZm8oJ3JldHJpZXZlIHVwZGF0ZWQgc3Vic2NyaXB0aW9uIGVyciAnLCBlcnIpXG4gICAgLy8gICB9IGVsc2Uge1xuICAgIC8vICAgICB3aW5zdG9uLmluZm8oJ8K7wrvCu8K7wrvCu8K7wrvCuyBncmV0cmlldmUgdXBkYXRlZCBzdWJzY3JpcHRpb246ICcsIHN1YnNjcmlwdGlvbik7XG5cbiAgICAvLyAgIH1cbiAgICAvLyB9KVxuICB9KVxuXG59KVxuXG5yb3V0ZXIuZ2V0KCcvOnN1YnNjcmlwdGlvbmlkJywgW3Bhc3Nwb3J0LmF1dGhlbnRpY2F0ZShbJ2Jhc2ljJywgJ2p3dCddLCB7IHNlc3Npb246IGZhbHNlIH0pLCB2YWxpZHRva2VuXSwgZnVuY3Rpb24gKHJlcSwgcmVzKSB7XG5cbiAgU3Vic2NyaXB0aW9uUGF5bWVudC5maW5kKHsgc3Vic2NyaXB0aW9uX2lkOiByZXEucGFyYW1zLnN1YnNjcmlwdGlvbmlkIH0pLnNvcnQoeyAnb2JqZWN0LmNyZWF0ZWQnOiBcImFzY1wiIH0pLmV4ZWMoZnVuY3Rpb24gKGVyciwgc3Vic2NyaXB0aW9uUGF5bWVudHMpIHtcbiAgICBpZiAoZXJyKSB7XG4gICAgICB3aW5zdG9uLmVycm9yKCctLSA+IEdFVCBTVUJTQ1JJUFRJT04gUEFZTUVOVFMgRVJST1J0ICcsIHByb2plY3QpO1xuICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAwKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogZXJyIH0pXG4gICAgfVxuICAgIHJlcy5qc29uKHN1YnNjcmlwdGlvblBheW1lbnRzKTtcbiAgfSk7XG5cbn0pO1xuXG4vLyAgUkVUUklFVkUgVEhFIENVUlJFTlQgU1VCU0NSSVBUSU9OIEZST00gU1RSSVBFXG5yb3V0ZXIuZ2V0KCcvc3RyaXBlc3Vicy86c3Vic2NyaXB0aW9uaWQnLCBbcGFzc3BvcnQuYXV0aGVudGljYXRlKFsnYmFzaWMnLCAnand0J10sIHsgc2Vzc2lvbjogZmFsc2UgfSksIHZhbGlkdG9rZW5dLCBmdW5jdGlvbiAocmVxLCByZXMpIHtcblxuICB3aW5zdG9uLmluZm8oJy0tID4gc3Vic2NyaXB0aW9uIGdldCBieSBpZCBmcmVxLnBhcmFtcy5zdWJzY3JpcHRpb25pZCAnLCByZXEucGFyYW1zLnN1YnNjcmlwdGlvbmlkKTtcbiAgdmFyIHN0cmlwZSA9IHJlcXVpcmUoJ3N0cmlwZScpKGFwaVNlY3JldEtleSk7XG5cbiAgc3RyaXBlLnN1YnNjcmlwdGlvbnMucmV0cmlldmUoXG4gICAgcmVxLnBhcmFtcy5zdWJzY3JpcHRpb25pZCxcbiAgICBmdW5jdGlvbiAoZXJyLCBzdWJzY3JpcHRpb24pIHtcbiAgICAgIGlmIChlcnIpIHtcbiAgICAgICAgd2luc3Rvbi5lcnJvcignLS0gPiBzdWJzY3JpcHRpb24gZ2V0IGJ5IGlkIGZyb20gc3RyaXBlICBlcnIgJywgZXJyKTtcbiAgICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAwKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogZXJyIH0pXG4gICAgICB9XG4gICAgICB3aW5zdG9uLmluZm8oJy0tID4gc3Vic2NyaXB0aW9uIGdldCBieSBpZCBmcm9tIHN0cmlwZSAnLCBzdWJzY3JpcHRpb24pXG4gICAgICByZXMuanNvbihzdWJzY3JpcHRpb24pO1xuICAgIH1cbiAgKTtcbn0pO1xuXG4vLyAgUkVUUklFVkUgVEhFIENIRUNLT1VUIFNFU1NJT05cbnJvdXRlci5nZXQoJy9jaGVja291dFNlc3Npb24vOnNlc3Npb25pZCcsIFtwYXNzcG9ydC5hdXRoZW50aWNhdGUoWydiYXNpYycsICdqd3QnXSwgeyBzZXNzaW9uOiBmYWxzZSB9KSwgdmFsaWR0b2tlbl0sIGZ1bmN0aW9uIChyZXEsIHJlcykge1xuXG4gIHdpbnN0b24uaW5mbygnLS0gPiBjaGVja291dFNlc3Npb24gcGFyYW1zLnNlc3Npb25pZCAnLCByZXEucGFyYW1zLnNlc3Npb25pZCk7XG4gIHZhciBzdHJpcGUgPSByZXF1aXJlKCdzdHJpcGUnKShhcGlTZWNyZXRLZXkpO1xuXG4gIHN0cmlwZS5jaGVja291dC5zZXNzaW9ucy5yZXRyaWV2ZShcbiAgICByZXEucGFyYW1zLnNlc3Npb25pZCxcbiAgICBmdW5jdGlvbiAoZXJyLCBzZXNzaW9uKSB7XG4gICAgICBpZiAoZXJyKSB7XG4gICAgICAgIHdpbnN0b24uaW5mbygnLS0gPiBjaGVja291dFNlc3Npb24gZ2V0IGJ5IGlkIGZyb20gc3RyaXBlICBlcnIgJywgZXJyKTtcbiAgICAgICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAwKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogZXJyIH0pXG4gICAgICB9XG4gICAgICB3aW5zdG9uLmluZm8oJy0tID4gY2hlY2tvdXRTZXNzaW9uIGdldCBieSBpZCBmcm9tIHN0cmlwZSAnLCBzZXNzaW9uKVxuICAgICAgcmVzLmpzb24oc2Vzc2lvbik7XG4gICAgfVxuICApO1xufSk7XG5cbi8vbmV3IGNvZGVcbnJvdXRlci5nZXQoJy9jdXN0b21lci86cHJvamVjdGlkJywgW3Bhc3Nwb3J0LmF1dGhlbnRpY2F0ZShbJ2Jhc2ljJywgJ2p3dCddLCB7IHNlc3Npb246IGZhbHNlIH0pLCB2YWxpZHRva2VuXSwgZnVuY3Rpb24gKHJlcSwgcmVzKSB7XG4gIHdpbnN0b24uZGVidWcoJ8K7wrvCuyDCu8K7wrsgZ2V0IGN1c3RvbWVyIGZyb20gZGIgJywgcmVxLnBhcmFtcylcbiAgdmFyIHByb2plY3RpZCA9IHJlcS5wYXJhbXMucHJvamVjdGlkXG5cbiAgd2luc3Rvbi5kZWJ1ZygnwrvCu8K7IMK7wrvCuyBnZXQgY3VzdG9tZXIgZnJvbSBkYiAtIHByb2plY3RpZCcsIHByb2plY3RpZCk7XG5cbiAgU3Vic2NyaXB0aW9uUGF5bWVudC5maW5kKHsgcHJvamVjdF9pZDogcHJvamVjdGlkLCBzdHJpcGVfZXZlbnQ6IFwiY2hlY2tvdXQuc2Vzc2lvbi5jb21wbGV0ZWRcIiB9LCBhc3luYyBmdW5jdGlvbiAoZXJyLCBzdWJzY3JpcHRpb24pIHtcbiAgICBpZiAoZXJyKSB7XG4gICAgICB3aW5zdG9uLmRlYnVnKCctLSA+IGdldCBjdXN0b21lciBmcm9tIGRiIC0gRXJyb3IgJywgZXJyKVxuICAgICAgcmV0dXJuIChlcnIpO1xuICAgIH1cbiAgICBpZiAoc3Vic2NyaXB0aW9uKSB7XG5cbiAgICAgIHN1YnNjcmlwdGlvblswXS5vYmplY3QuY3VzdG9tZXJcbiAgICAgIHdpbnN0b24uZGVidWcoJy0tID4gZ2V0IGN1c3RvbWVyIGZyb20gZGIgLSBzdWJzY3JpcHRpb24gPiBjdXN0b21lciBpZCAnLCBzdWJzY3JpcHRpb25bMF0ub2JqZWN0LmN1c3RvbWVyKTtcbiAgICAgIGNvbnN0IGN1c3RvbWVyaWQgPSBzdWJzY3JpcHRpb25bMF0ub2JqZWN0LmN1c3RvbWVyXG4gICAgICBjb25zdCBzdHJpcGUgPSByZXF1aXJlKFwic3RyaXBlXCIpKGFwaVNlY3JldEtleSk7XG5cbiAgICAgIGNvbnN0IGN1c3RvbWVyID0gYXdhaXQgc3RyaXBlLmN1c3RvbWVycy5yZXRyaWV2ZShcbiAgICAgICAgY3VzdG9tZXJpZFxuICAgICAgKTtcbiAgICAgIHdpbnN0b24uZGVidWcoJy0tID4gZ2V0IGN1c3RvbWVyIGZyb20gZGIgPiBjdXN0b21lciBmcm9tIHN0cmlwZSBBUEkgJywgY3VzdG9tZXIpXG5cbiAgICAgIGNvbnN0IHBheW1lbnRNZXRob2RzID0gYXdhaXQgc3RyaXBlLnBheW1lbnRNZXRob2RzLmxpc3Qoe1xuICAgICAgICBjdXN0b21lcjogY3VzdG9tZXJpZCxcbiAgICAgICAgdHlwZTogJ2NhcmQnLFxuICAgICAgfSk7XG4gICAgICBjdXN0b21lclsncGF5bWVudE1ldGhvZHMnXSA9IHBheW1lbnRNZXRob2RzO1xuICAgICAgd2luc3Rvbi5kZWJ1ZygnLS0gPiBnZXQgY3VzdG9tZXIgZnJvbSBkYiA+IGN1c3RvbWVyICsgcGF5bWVudE1ldGhvZHMgJywgc3Vic2NyaXB0aW9uKVxuICAgICAgcmVzLmpzb24oY3VzdG9tZXIpXG4gICAgfVxuICB9KVxufSk7XG5cbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIFVwZGF0ZWQgY3VzdG9tZXJcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbnJvdXRlci5wb3N0KCcvY3VzdG9tZXJzLzpjdXN0b21lcmlkJywgW3Bhc3Nwb3J0LmF1dGhlbnRpY2F0ZShbJ2Jhc2ljJywgJ2p3dCddLCB7IHNlc3Npb246IGZhbHNlIH0pLCB2YWxpZHRva2VuXSwgYXN5bmMgZnVuY3Rpb24gKHJlcSwgcmVzKSB7XG4gIHZhciBjdXN0b21lcmlkID0gcmVxLnBhcmFtcy5jdXN0b21lcmlkO1xuICB3aW5zdG9uLmRlYnVnKCfCu8K7wrsgwrvCu8K7ICB1cGRhdGUgY3VzdG9tZXIgLSBjdXN0b21lcmlkIGZyb20gcGFyYW1zICcsIGN1c3RvbWVyaWQpO1xuICB3aW5zdG9uLmRlYnVnKCfCu8K7wrsgwrvCu8K7ICB1cGRhdGUgY3VzdG9tZXIgLSBjYyBmcm9tIGJvZHkgJywgcmVxLmJvZHkpO1xuXG4gIGNvbnN0IHN0cmlwZSA9IHJlcXVpcmUoXCJzdHJpcGVcIikoYXBpU2VjcmV0S2V5KTtcblxuICBsZXQgcGF5bWVudE1ldGhvZDtcbiAgdHJ5IHtcbiAgICBwYXltZW50TWV0aG9kID0gYXdhaXQgc3RyaXBlLnBheW1lbnRNZXRob2RzLmNyZWF0ZSh7XG4gICAgICB0eXBlOiAnY2FyZCcsXG4gICAgICBjYXJkOiB7XG4gICAgICAgIG51bWJlcjogcmVxLmJvZHkuY3JlZGl0X2NhcmRfbnVtLFxuICAgICAgICBleHBfbW9udGg6IHJlcS5ib2R5LmV4cGlyYXRpb25fZGF0ZV9tb250aCxcbiAgICAgICAgZXhwX3llYXI6IHJlcS5ib2R5LmV4cGlyYXRpb25fZGF0ZV95ZWFyLFxuICAgICAgICBjdmM6IHJlcS5ib2R5LmNyZWRpdF9jYXJkX2N2YyxcbiAgICAgIH0sXG4gICAgfSk7XG4gIH0gY2F0Y2ggKGUpIHtcbiAgICB3aW5zdG9uLmVycm9yKCfCu8K7wrsgwrvCu8K7ICBwYXltZW50TWV0aG9kIGNyZWF0ZSAgZXJyb3IgJywgZSlcbiAgICByZXR1cm4gcmVzLnN0YXR1cyg1MDIpLnNlbmQoeyBzdWNjZXNzOiBmYWxzZSwgbXNnOiBlIH0pXG5cbiAgfVxuICB3aW5zdG9uLmRlYnVnKCfCu8K7wrsgwrvCu8K7ICBwYXltZW50TWV0aG9kJywgcGF5bWVudE1ldGhvZClcblxuICB0cnkge1xuICAgIGNvbnN0IF9wYXltZW50TWV0aG9kID0gYXdhaXQgc3RyaXBlLnBheW1lbnRNZXRob2RzLmF0dGFjaChcbiAgICAgIHBheW1lbnRNZXRob2QuaWQsXG4gICAgICB7IGN1c3RvbWVyOiBjdXN0b21lcmlkIH1cbiAgICApO1xuXG4gICAgd2luc3Rvbi5kZWJ1ZygnwrvCu8K7IMK7wrvCuyAgcGF5bWVudE1ldGhvZCBhdHRhY2hlZCAnLCBfcGF5bWVudE1ldGhvZClcbiAgfSBjYXRjaCAoZSkge1xuICAgIHdpbnN0b24uZXJyb3IoJ8K7wrvCuyDCu8K7wrsgIHBheW1lbnRNZXRob2QgYXR0YWNoZWQgIGVycm9yICcsIGUpXG4gICAgcmV0dXJuIHJlcy5zdGF0dXMoNTAxKS5zZW5kKHsgc3VjY2VzczogZmFsc2UsIG1zZzogZSB9KVxuICB9XG5cbiAgY29uc3QgX2N1c3RvbWVyID0gYXdhaXQgc3RyaXBlLmN1c3RvbWVycy51cGRhdGUoXG4gICAgY3VzdG9tZXJpZCxcbiAgICB7XG4gICAgICBpbnZvaWNlX3NldHRpbmdzOiB7IGRlZmF1bHRfcGF5bWVudF9tZXRob2Q6IHBheW1lbnRNZXRob2QuaWQgfVxuICAgIH0pO1xuICByZXMuanNvbihfY3VzdG9tZXIpO1xufSk7XG5cblxucm91dGVyLmdldCgnL3BheW1lbnRfbWV0aG9kcy86Y3VzdG9tZXJpZCcsIFtwYXNzcG9ydC5hdXRoZW50aWNhdGUoWydiYXNpYycsICdqd3QnXSwgeyBzZXNzaW9uOiBmYWxzZSB9KSwgdmFsaWR0b2tlbl0sIGFzeW5jIGZ1bmN0aW9uIChyZXEsIHJlcykge1xuICB3aW5zdG9uLmluZm8oJ2dldCBQYXltZW50TWV0aG9kcyBsaXN0IHJlcS5wYXJhbXMnLCByZXEucGFyYW1zKVxuICB2YXIgY3VzdG9tZXJfaWQgPSByZXEucGFyYW1zLmN1c3RvbWVyaWQ7XG4gIHdpbnN0b24uZGVidWcoJ2dldCBQYXltZW50TWV0aG9kcyBsaXN0IHJlcS5wYXJhbXMgPiBjdXN0b21lcl9pZCAnLCBjdXN0b21lcl9pZClcbiAgY29uc3Qgc3RyaXBlID0gcmVxdWlyZShcInN0cmlwZVwiKShhcGlTZWNyZXRLZXkpO1xuXG4gIGNvbnN0IGN1c3RvbWVyID0gYXdhaXQgc3RyaXBlLmN1c3RvbWVycy5yZXRyaWV2ZShcbiAgICBjdXN0b21lcl9pZFxuICApO1xuXG4gIGNvbnN0IGRlZmF1bHRfcGF5bWVudF9tZXRob2RfaWQgPSBjdXN0b21lci5pbnZvaWNlX3NldHRpbmdzLmRlZmF1bHRfcGF5bWVudF9tZXRob2RcbiAgbGV0IHBheW1lbnRNZXRob2RzO1xuICB0cnkge1xuICAgIHBheW1lbnRNZXRob2RzID0gYXdhaXQgc3RyaXBlLnBheW1lbnRNZXRob2RzLmxpc3Qoe1xuICAgICAgY3VzdG9tZXI6IGN1c3RvbWVyX2lkLFxuICAgICAgdHlwZTogJ2NhcmQnLFxuICAgIH0pO1xuICAgIHdpbnN0b24uZGVidWcoJ2dldCBQYXltZW50TWV0aG9kcyBsaXN0ID4gcGF5bWVudE1ldGhvZHMgJywgcGF5bWVudE1ldGhvZHMpXG4gIH0gY2F0Y2ggKGUpIHtcbiAgICByZXR1cm4gcmVzLnN0YXR1cyg1MDEpLnNlbmQoeyBzdWNjZXNzOiBmYWxzZSwgbXNnOiBlIH0pXG4gIH1cblxuICB3aW5zdG9uLmRlYnVnKCdnZXQgUGF5bWVudE1ldGhvZHMgbGlzdCA+IHBheW1lbnRNZXRob2RzID4gZGVmYXVsdF9wYXltZW50X21ldGhvZF9pZCAnLCBkZWZhdWx0X3BheW1lbnRfbWV0aG9kX2lkKVxuICBwYXltZW50TWV0aG9kcy5kYXRhLmZvckVhY2gocGF5bWVudE1ldGhvZCA9PiB7XG4gICAgaWYgKHBheW1lbnRNZXRob2QuaWQgIT09IGRlZmF1bHRfcGF5bWVudF9tZXRob2RfaWQpIHtcbiAgICAgIHdpbnN0b24uZGVidWcoJ2dldCBQYXltZW50TWV0aG9kcyBsaXN0ID4gcGF5bWVudE1ldGhvZHMgPiBwYXltZW50TWV0aG9kcy5kYXRhICcsIHBheW1lbnRNZXRob2QuaWQpXG4gICAgICBkZXRhY2hQYXltZW50RnVuYyhwYXltZW50TWV0aG9kLmlkLCBmdW5jdGlvbiAocmVzdWx0KSB7XG4gICAgICAgIHdpbnN0b24uaW5mbygnZGV0YWNoUGF5bWVudEZ1bmMgcmVzdWx0ICcsIHJlc3VsdClcbiAgICAgIH0pXG4gICAgfVxuICB9KTtcbiAgcmVzLmpzb24ocGF5bWVudE1ldGhvZHMpXG59KVxuXG5hc3luYyBmdW5jdGlvbiBkZXRhY2hQYXltZW50RnVuYyhwYXltZW50TWV0aG9kaWQsIGNhbGxiYWNrKSB7XG4gIHdpbnN0b24uZGVidWcoJ2RldGFjaFBheW1lbnRGdW5jdCA+IHBheW1lbnRNZXRob2RpZCAnLCBwYXltZW50TWV0aG9kaWQpXG4gIGNvbnN0IHN0cmlwZSA9IHJlcXVpcmUoXCJzdHJpcGVcIikoYXBpU2VjcmV0S2V5KTtcbiAgbGV0IHBheW1lbnRNZXRob2Q7XG4gIHRyeSB7XG4gICAgcGF5bWVudE1ldGhvZCA9IGF3YWl0IHN0cmlwZS5wYXltZW50TWV0aG9kcy5kZXRhY2goXG4gICAgICBwYXltZW50TWV0aG9kaWRcbiAgICApO1xuICAgIGNhbGxiYWNrKHBheW1lbnRNZXRob2QpXG4gIH0gY2F0Y2ggKGUpIHtcbiAgICBjYWxsYmFjayhlKVxuICB9XG59XG5cblxuXG4vLyBjdXJsIC1YIFBPU1QgLXUgYW5kcmVhLmxlb0BmMjEuaXQ6MTIzNDU2IC1IICdDb250ZW50LVR5cGU6YXBwbGljYXRpb24vanNvbicgLWQgJ3tcInR5cGVcIjpcInBheW1lbnRfaW50ZW50LnN1Y2NlZWRlZFwifScgIGh0dHA6Ly9sb2NhbGhvc3Q6MzAwMC9tb2R1bGVzL3BheW1lbnRzL3N0cmlwZS93ZWJob29rXG5cbi8vIHJvdXRlci5wb3N0KCcvd2ViaG9vaycsIGZ1bmN0aW9uKHJlcXVlc3QsIHJlc3BvbnNlKSB7XG4vLyAgIHdpbnN0b24uaW5mbygnc3RyaXBlIHdlYmhvb2snKTtcblxuLy8gLy8gTWF0Y2ggdGhlIHJhdyBib2R5IHRvIGNvbnRlbnQgdHlwZSBhcHBsaWNhdGlvbi9qc29uXG4vLyAvLyBhcHAucG9zdCgnJywgYm9keVBhcnNlci5yYXcoe3R5cGU6ICdhcHBsaWNhdGlvbi9qc29uJ30pLCAocmVxdWVzdCwgcmVzcG9uc2UpID0+IHtcbi8vICAgICBsZXQgZXZlbnQgPSByZXF1ZXN0LmJvZHk7XG4vLyAgICAgd2luc3Rvbi5pbmZvKCdldmVudCcsZXZlbnQpOyAgXG5cbi8vICAgICAvLyBIYW5kbGUgdGhlIGV2ZW50XG4vLyAgICAgc3dpdGNoIChldmVudC50eXBlKSB7XG4vLyAgICAgICBjYXNlICdwYXltZW50X2ludGVudC5zdWNjZWVkZWQnOlxuLy8gICAgICAgICBjb25zdCBwYXltZW50SW50ZW50ID0gZXZlbnQuZGF0YS5vYmplY3Q7XG4vLyAgICAgICAgIGhhbmRsZVBheW1lbnRJbnRlbnRTdWNjZWVkZWQocGF5bWVudEludGVudCk7XG4vLyAgICAgICAgIGJyZWFrO1xuLy8gICAgICAgY2FzZSAncGF5bWVudF9tZXRob2QuYXR0YWNoZWQnOlxuLy8gICAgICAgICBjb25zdCBwYXltZW50TWV0aG9kID0gZXZlbnQuZGF0YS5vYmplY3Q7XG4vLyAgICAgICAgIGhhbmRsZVBheW1lbnRNZXRob2RBdHRhY2hlZChwYXltZW50TWV0aG9kKTtcbi8vICAgICAgICAgYnJlYWs7XG4vLyAgICAgICAvLyAuLi4gaGFuZGxlIG90aGVyIGV2ZW50IHR5cGVzXG4vLyAgICAgICBkZWZhdWx0OlxuLy8gICAgICAgICAvLyBVbmV4cGVjdGVkIGV2ZW50IHR5cGVcbi8vICAgICAgICAgcmV0dXJuIHJlc3BvbnNlLnN0YXR1cyg0MDApLmVuZCgpO1xuLy8gICAgIH1cblxuLy8gICAgIC8vIFJldHVybiBhIHJlc3BvbnNlIHRvIGFja25vd2xlZGdlIHJlY2VpcHQgb2YgdGhlIGV2ZW50XG4vLyAgICAgcmVzcG9uc2UuanNvbih7cmVjZWl2ZWQ6IHRydWV9KTtcbi8vIH0pO1xuXG5cblxuXG5tb2R1bGUuZXhwb3J0cyA9IHJvdXRlcjsiXSwiZmlsZSI6InBheW1lbnRzL3N0cmlwZS9pbmRleC5qcyJ9\n"
  },
  {
    "path": "pubmodules/scheduler/index.js",
    "content": "const taskRunner = require(\"./taskRunner\");\nmodule.exports = {taskRunner:taskRunner};"
  },
  {
    "path": "pubmodules/scheduler/taskRunner.js",
    "content": "\n'use strict';\n\n\nvar winston = require('../../config/winston');\nvar closeBotUnresponsiveRequestTask = require('./tasks/closeBotUnresponsiveRequestTask');\nvar closeAgentUnresponsiveRequestTask = require('./tasks/closeAgentUnresponsiveRequestTask');\n\nclass TaskRunner {\n\nconstructor() {\n  this.enabled = process.env.TASK_SCHEDULER_ENABLED || \"true\"\n}\n\nstart() {\n    // var that = this;\n    if (this.enabled == \"true\") {\n      winston.info(\"TaskRunner started\" );\n      closeBotUnresponsiveRequestTask.run();\n      closeAgentUnresponsiveRequestTask.run();\n    }else {\n      winston.info(\"TaskRunner is disabled\" );\n    }\n    \n\n}\n\n   \n}\n \n \n \n \nvar taskRunner = new TaskRunner();\n\n\nmodule.exports = taskRunner;"
  },
  {
    "path": "pubmodules/scheduler/tasks/closeAgentUnresponsiveRequestTask.js",
    "content": "'use strict';\n\nconst schedule = require('node-schedule');\nconst winston = require('../../../config/winston');\nconst Request = require(\"../../../models/request\");\nconst requestService = require(\"../../../services/requestService\");\n\n/**\n * Task scheduler for automatically closing agent unresponsive requests.\n * Finds requests with agents that haven't been updated within a specified timeout\n * and closes them automatically.\n */\nclass CloseAgentUnresponsiveRequestTask {\n\n  constructor() {\n\n    this.enabled = process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_ENABLE || \"false\"; \n    this.cronExp = process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION || '*/30 * * * *'; //every 30 minutes\n    this.queryAfterTimeout = parseInt(process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_AFTER_TIMEOUT) || 5 * 24 * 60 * 60 * 1000; //five days ago // 86400000 a day\n    this.queryLimit = parseInt(process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_QUERY_LIMIT) || 10;\n    this.queryProject = process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_QUERY_FILTER_ONLY_PROJECT;\n    this.delayBeforeClosing = parseInt(process.env.CLOSE_AGENT_UNRESPONSIVE_REQUESTS_DELAY) || 1000;\n\n    if (this.queryProject) {\n      winston.info(\"CloseAgentUnresponsiveRequestTask filter only by projects enabled: \" + this.queryProject );\n    }\n    \n  }\n\n  /**\n   * Starts the scheduler if enabled.\n   */\n  run() {    \n      if (this.enabled === \"true\") {\n        winston.info(\"CloseAgentUnresponsiveRequestTask started\" );\n        this.scheduleUnresponsiveRequests();\n      } else {\n        winston.info(\"CloseAgentUnresponsiveRequestTask disabled\" );\n      }\n  }\n\n  /**\n   * Schedules the recurring job to find and close unresponsive requests.\n   */\n  scheduleUnresponsiveRequests() {\n    winston.info(\n      `CloseAgentUnresponsiveRequestTask task scheduleUnresponsiveRequests launched with ` +\n      `closeAfter: ${this.queryAfterTimeout} milliseconds, ` +\n      `cron expression: ${this.cronExp} and query limit: ${this.queryLimit}`\n    );\n\n    schedule.scheduleJob(this.cronExp, (fireDate) => {\n      winston.debug(`CloseAgentUnresponsiveRequestTask ScheduleUnresponsiveRequests job was supposed to run at ${fireDate}, but actually ran at ${new Date()}`);\n      this.findUnresponsiveRequests();\n    });\n  }\n\n\n  /**\n   * Finds unresponsive agent requests and schedules their closure.\n   * Queries for requests with agents that haven't been updated within the timeout period.\n   */\n  findUnresponsiveRequests() {\n    const cutoffDate = new Date(Date.now() - this.queryAfterTimeout);\n    const query = {\n      hasBot: false,\n      status: { $lt: 1000 },\n      updatedAt: { $lte: cutoffDate },\n      workingStatus: { $ne: 'pending' }\n    };\n\n    if (this.queryProject) {\n      try {\n        query.id_project = JSON.parse(this.queryProject);\n      } catch (err) {\n        winston.error(\"CloseAgentUnresponsiveRequestTask error parsing queryProject filter\", err);\n        return;\n      }\n    }\n\n    winston.debug(\"CloseAgentUnresponsiveRequestTask query\",query);\n    \n    //console.log(\"[CloseUnresponsive] Searching with query: \", query);\n    Request.find(query)\n      .sort({ updatedAt: 'asc' })\n      .limit(this.queryLimit)\n      .hint({ status: 1, hasBot: 1, updatedAt: 1 })\n      .exec((err, requests) => {\n        if (err) {\n            winston.error(\"CloseAgentUnresponsiveRequestTask error getting unresponsive requests \", err);\n            return;\n        }\n        \n        if (!requests || requests.length === 0) {\n          winston.verbose(\"CloseAgentUnresponsiveRequestTask no unresponsive requests found \");\n          return;\n        }\n\n        //console.log(\"[CloseUnresponsive] Found \", requests.length, \" unresponsive requests\");\n\n        winston.verbose(\"CloseAgentUnresponsiveRequestTask: found \" + requests.length +  \" unresponsive requests\");\n        winston.debug(\"CloseAgentUnresponsiveRequestTask: found unresponsive requests \", requests);\n\n        this.scheduleRequestClosures(requests);\n\n    });\n  }\n\n  /**\n   * Schedules the closure of multiple requests with staggered delays.\n   * @param {Array} requests - Array of request objects to close\n   */\n  scheduleRequestClosures(requests) {\n    requests.forEach((request, index) => {\n      // Stagger delays: 2 * delayBeforeClosing * (index + 1)\n      const delay = 2 * this.delayBeforeClosing * (index + 1);\n      winston.debug(`CloseAgentUnresponsiveRequestTask: scheduling closure with delay: ${delay}ms for request_id: ${request.request_id}`);\n\n      setTimeout(() => {\n        this.closeRequest(request);\n      }, delay);\n    });\n  }\n\n  /**\n   * Closes a single unresponsive request.\n   * @param {Object} request - The request object to close\n   */\n  closeRequest(request) {\n    winston.debug(`CloseAgentUnresponsiveRequestTask: processing unresponsive request: ${request.first_text}`);\n\n    const closedBy = \"_bot_unresponsive\";\n    const shouldSkipStatsUpdate = false;\n    const shouldNotify = false;\n\n    requestService.closeRequestByRequestId(\n      request.request_id,\n      request.id_project,\n      shouldSkipStatsUpdate,\n      shouldNotify,\n      closedBy\n    ).then(() => {\n      winston.info(`CloseAgentUnresponsiveRequestTask: Request closed with request_id: ${request.request_id}`);\n    }).catch((err) => {\n      const hideErrors = process.env.HIDE_CLOSE_REQUEST_ERRORS === true || process.env.HIDE_CLOSE_REQUEST_ERRORS === \"true\";\n      \n      if (!hideErrors) {\n        winston.error(`CloseAgentUnresponsiveRequestTask: Error closing the request with request_id: ${request.request_id}`, err);\n      }\n    });\n\n  }\n   \n}\n \nconst closeAgentUnresponsiveRequestTask = new CloseAgentUnresponsiveRequestTask();\n\nmodule.exports = closeAgentUnresponsiveRequestTask;"
  },
  {
    "path": "pubmodules/scheduler/tasks/closeBotUnresponsiveRequestTask.js",
    "content": "'use strict';\n\nconst schedule = require('node-schedule');\nconst winston = require('../../../config/winston');\nconst Request = require(\"../../../models/request\");\nconst requestService = require(\"../../../services/requestService\");\n\n/**\n * Task scheduler for automatically closing bot unresponsive requests.\n * Finds requests with bots that haven't been updated within a specified timeout\n * and closes them automatically.\n */\nclass CloseBotUnresponsiveRequestTask {\n\n  constructor() {\n\n    this.enabled = process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_ENABLE || \"true\";\n    this.cronExp = process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION || '*/5 * * * *'; // every 5 minutes  // every 30 seconds '*/30 * * * * *';\n    this.queryAfterTimeout = parseInt(process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_AFTER_TIMEOUT) || 2 * 24 * 60 * 60 * 1000; //two days ago //172800000 two days // 86400000 a day\n    this.queryLimit = parseInt(process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_QUERY_LIMIT) || 10;\n    this.queryProject = process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_QUERY_FILTER_ONLY_PROJECT; //example in PRE: {\"$in\":[\"5fc224ce05416200342af18a\",\"5fb3e3cb0150a00034ab77d5\"]}\n    this.delayBeforeClosing = parseInt(process.env.CLOSE_BOT_UNRESPONSIVE_REQUESTS_DELAY) || 1000;\n\n    if (this.queryProject) {\n      winston.info(\"CloseBotUnresponsiveRequestTask filter only by projects enabled: \" + this.queryProject);\n    }\n  }\n\n  /**\n   * Starts the scheduler if enabled.\n   */\n  run() {\n    if (this.enabled === \"true\") {\n      winston.info(\"CloseBotUnresponsiveRequestTask started\");\n      this.scheduleUnresponsiveRequests();\n    } else {\n      winston.info(\"CloseBotUnresponsiveRequestTask disabled\");\n    }\n  }\n\n  /**\n   * Schedules the recurring job to find and close unresponsive requests.\n   * Includes a random delay to avoid concurrent execution in cluster environments.\n   */\n  scheduleUnresponsiveRequests() {\n    winston.info(\n      `CloseBotUnresponsiveRequestTask task scheduleUnresponsiveRequests launched with ` +\n      `closeAfter: ${this.queryAfterTimeout} milliseconds, ` +\n      `cron expression: ${this.cronExp} and query limit: ${this.queryLimit}`\n    );\n\n    schedule.scheduleJob(this.cronExp, (fireDate) => {\n      const randomDelay = Math.random() * 1000;\n      winston.debug(`CloseBotUnresponsiveRequestTask random delay: ${randomDelay}ms`);\n\n      setTimeout(() => {\n        winston.debug(\n          `CloseBotUnresponsiveRequestTask scheduleUnresponsiveRequests job was supposed to run at ${fireDate}, ` +\n          `but actually ran at ${new Date()}`\n        );\n        this.findUnresponsiveRequests();\n      }, randomDelay);\n\n    });\n  }\n\n  /**\n   * Finds unresponsive bot requests and schedules their closure.\n   * Queries for requests with bots that haven't been updated within the timeout period.\n   */\n  findUnresponsiveRequests() {\n    const cutoffDate = new Date(Date.now() - this.queryAfterTimeout);\n    const query = { \n      hasBot: true, \n      status: { $lt: 1000 }, \n      updatedAt: { $lte: cutoffDate },\n      workingStatus: { $ne: 'pending' }\n    };\n\n    if (this.queryProject) {\n      try {\n        query.id_project = JSON.parse(this.queryProject);\n      } catch (err) {\n        winston.error(\"CloseBotUnresponsiveRequestTask error parsing queryProject filter\", err);\n        return;\n      }\n    }\n\n    winston.debug(\"CloseBotUnresponsiveRequestTask query\", query);\n\n    //console.log(\"[CloseUnresponsive] Searching with query: \", query);\n    Request.find(query)\n      .sort({ updatedAt: 'asc' })\n      .limit(this.queryLimit)\n      .hint({ status: 1, hasBot: 1, updatedAt: 1 })\n      .exec((err, requests) => {\n        if (err) {\n          winston.error(\"CloseBotUnresponsiveRequestTask error getting unresponsive requests \", err);\n          return;\n        }\n\n        if (!requests || requests.length === 0) {\n          winston.verbose(\"CloseBotUnresponsiveRequestTask no unresponsive requests found \");\n          return;\n        }\n\n        //console.log(\"[CloseUnresponsive] Found \", requests.length, \" unresponsive requests\");\n\n        winston.info(\"CloseBotUnresponsiveRequestTask: found \" + requests.length + \" unresponsive requests\");\n        winston.debug(\"CloseBotUnresponsiveRequestTask: found unresponsive requests \", requests);\n\n        this.scheduleRequestClosures(requests);\n      });\n  }\n\n  /**\n   * Schedules the closure of multiple requests with staggered delays.\n   * @param {Array} requests - Array of request objects to close\n   */\n  scheduleRequestClosures(requests) {\n    requests.forEach((request, index) => {\n      // Stagger delays: 2 * delayBeforeClosing * (index + 1)\n      const delay = 2 * this.delayBeforeClosing * (index + 1);\n      winston.debug(`CloseBotUnresponsiveRequestTask: scheduling closure with delay: ${delay}ms for request_id: ${request.request_id}`);\n\n      setTimeout(() => {\n        this.closeRequest(request);\n      }, delay);\n    });\n  }\n\n  /**\n   * Closes a single unresponsive request.\n   * @param {Object} request - The request object to close\n   */\n  closeRequest(request) {\n    winston.debug(`CloseBotUnresponsiveRequestTask: processing unresponsive request: ${request.first_text}`);\n\n    const closedBy = \"_bot_unresponsive\";\n    const shouldSkipStatsUpdate = false;\n    const shouldNotify = false;\n\n    requestService.closeRequestByRequestId(\n      request.request_id,\n      request.id_project,\n      shouldSkipStatsUpdate,\n      shouldNotify,\n      closedBy\n    ).then(() => {\n      winston.info(`CloseBotUnresponsiveRequestTask: Request closed with request_id: ${request.request_id}`);\n    }).catch((err) => {\n      const hideErrors = process.env.HIDE_CLOSE_REQUEST_ERRORS === true || process.env.HIDE_CLOSE_REQUEST_ERRORS === \"true\";\n\n      if (!hideErrors) {\n        winston.error(`CloseBotUnresponsiveRequestTask: Error closing the request with request_id: ${request.request_id}`, err);\n      }\n    });\n\n  }\n\n}\n\nconst closeBotUnresponsiveRequestTask = new CloseBotUnresponsiveRequestTask();\n\nmodule.exports = closeBotUnresponsiveRequestTask;"
  },
  {
    "path": "pubmodules/scheduler/tasks/requestTaskSchedulerAgenda.js",
    "content": "\n'use strict';\n\n\nvar winston = require('../../config/winston');\nvar Request = require(\"../../models/request\");\nvar requestService = require(\"../../services/requestService\");\n\nvar Agenda = require(\"agenda\");\nconst mongoConnectionString = 'mongodb://127.0.0.1/agenda'; \nconst agenda = new Agenda({db: {address: mongoConnectionString}});\n\n\n//UI \n// npx agendash --db=mongodb://localhost/agenda --collection=agendaJobs --port=3001\n\nclass RequestTaskScheduler {\n\nconstructor() {\n  this.closeUnresponsiveRequestsCronExp = process.env.CLOSE_UNRESPONSIVE_REQUESTS_CRON_EXPRESSION || '*/30 * * * * *'; // '*/1 * * * * *'every second\n  this.closeUnresponsiveRequestsAfterTimeout = process.env.CLOSE_UNRESPONSIVE_REQUESTS_AFTER_TIMEOUT || 2 * 24 * 60 * 60 * 1000; //two days\n}\n\nrun() {\n    // var that = this;\n    // findUnresponsiveRequests();\n    winston.info(\"RequestTaskScheduler listen started\" );\n    this.scheduleUnresponsiveRequests();\n}\n\nscheduleUnresponsiveRequests() {\n  var that = this;\n  winston.info(\"RequestTaskScheduler task scheduleUnresponsiveRequests launched with closeAfter : \" + this.closeUnresponsiveRequestsAfterTimeout + \" cron expression: \" + this.closeUnresponsiveRequestsCronExp);\n\n //https://crontab.guru/examples.html\n//  var s= schedule.scheduleJob(this.closeUnresponsiveRequestsCronExp, function(fireDate){\n//     winston.info('This job was supposed to run at ' + fireDate + ', but actually ran at ' + new Date());\n//     that.findUnresponsiveRequests(); \n//   });\n\n  agenda.define('closeUnresponsiveRequests', async job => {\n    that.findUnresponsiveRequests(); \n  });\n   \n  (async function() { // IIFE to give access to async/await\n    await agenda.start();\n     \n    // Alternatively, you could also do:\n    await agenda.every(that.closeUnresponsiveRequestsCronExp, 'closeUnresponsiveRequests');\n  })();\n\n\n}\n\n\nfindUnresponsiveRequests() {\n    \n    // togli\n    this.closeUnresponsiveRequestsAfterTimeout = 1;\n\n  // db.getCollection('requests').find({\"hasBot\":true, \"status\": { \"$lt\": 1000 }, \"createdAt\":  { \"$lte\" :new ISODate(\"2020-11-28T20:15:31Z\")} }).count()\n    var query = {hasBot:true, status: { $lt: 1000 }, createdAt:  { $lte :new Date(Date.now() - this.closeUnresponsiveRequestsAfterTimeout ).toISOString()} };\n\n    // togli\n    query.id_project = \"5fc224ce05416200342af18a\";\n    \n    winston.info(\"**********query\",query);\n\n    Request.find(query).limit(1).exec(function(err, requests) {\n      //it is limited to 1000 results but after same days it all ok\n      if (err) {\n          winston.error(\"RequestTaskScheduler error getting unresponsive requests \", err);\n          return 0;\n      }\n      if (!requests || (requests && requests.length==0)) {\n          winston.info(\"RequestTaskScheduler no unresponsive requests found \");\n          return 0;\n      }\n\n      winston.info(\"********unresponsive requests \", requests);\n      \n      requests.forEach(request => {\n        winston.info(\"********unresponsive request \", request);\n\n        return requestService.closeRequestByRequestId(request.request_id, request.id_project, false, false).then(function(updatedStatusRequest) {\n          winston.info(\"Request closed\");\n          // winston.info(\"Request closed\",updatedStatusRequest);\n        }).catch(function(err) {\n          winston.error(\"Error closing the request\",err);\n        })\n\n      });\n\n    \n\n    });\n  }\n\n\n  \n\n   \n}\n \n \n \n \nvar requestTaskScheduler = new RequestTaskScheduler();\n\n\nmodule.exports = requestTaskScheduler;"
  },
  {
    "path": "pubmodules/sms/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst sms = require(\"@tiledesk/tiledesk-sms-connector\");\nconst smsRoute = sms.router;\n\n\nmodule.exports = { listener: listener, smsRoute: smsRoute }"
  },
  {
    "path": "pubmodules/sms/listener.js",
    "content": "const sms = require(\"@tiledesk/tiledesk-sms-connector\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\nconst mongoose = require(\"mongoose\");\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('SMS apiUrl: ' + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"SMS Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"SMS config databaseUri: \" + config.databaseUri);\n        }\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \"+ port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\"\n        winston.debug(\"Redis host: \"+ host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \"+ password);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME\n        }\n\n        let log = process.env.SMS_LOG || false\n        winston.debug(\"SMS log: \" + log);\n\n\n        sms.startApp({\n            MONGODB_URI: config.databaseUri,          \n            dbconnection: dbConnection,\n            BASE_URL: apiUrl + \"/modules/sms\",\n            BRAND_NAME: brand_name,\n            REDIS_HOST: host,\n            REDIS_PORT: port,\n            REDIS_PASSWORD: password,\n            log: log\n        })\n        \n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;\n\n"
  },
  {
    "path": "pubmodules/telegram/index.js",
    "content": "const listener = require('./listener');\n\nconst telegram = require(\"@tiledesk/tiledesk-telegram-connector\");\nconst telegramRoute = telegram.router;\n\nmodule.exports = { listener: listener, telegramRoute: telegramRoute }"
  },
  {
    "path": "pubmodules/telegram/listener.js",
    "content": "const telegram = require(\"@tiledesk/tiledesk-telegram-connector\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\nconst mongoose = require(\"mongoose\");\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info(\"telegram apiUrl: \" + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"Telegram Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"telegram config databaseUri: \" + config.databaseUri);\n        }\n\n        let telegram_api_url = process.env.TELEGRAM_API_URL || config.telegramApiUrl || \"https://api.telegram.org/bot\"\n        winston.debug(\"Telegram api url: \" + telegram_api_url);\n\n        let telegram_file_url = process.env.TELEGRAM_FILE_URL || config.telegramFileUrl || \"https://api.telegram.org/file/bot\"\n        winston.debug(\"Telegram file url: \" + telegram_file_url);\n\n        let log = process.env.TELEGRAM_LOG || 'debug'\n        winston.debug(\"Telegram log: \" + log);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME\n        }\n\n        telegram.startApp({\n            MONGODB_URL: config.databaseUri,\n            dbconnection: dbConnection,\n            API_URL: apiUrl,\n            TELEGRAM_API_URL: telegram_api_url,\n            TELEGRAM_FILE_URL: telegram_file_url,\n            BASE_URL: apiUrl + \"/modules/telegram\",\n            APPS_API_URL: apiUrl + \"/modules/apps\",\n            BRAND_NAME: brand_name,\n            log: log\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Tiledesk Telegram Connector proxy server successfully started\");\n            } else {\n                winston.info(\"unable to start Tiledesk Telegram Connector. \" + err);\n            }\n        })\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/tilebot/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst tilebot = require(\"@tiledesk/tiledesk-tybot-connector\");\nconst tilebotRoute = tilebot.router;\n\n\n\n\n\nmodule.exports = { listener: listener, tilebotRoute: tilebotRoute };\n\n"
  },
  {
    "path": "pubmodules/tilebot/listener.js",
    "content": "const botEvent = require('../../event/botEvent');\nvar Faq_kb = require(\"../../models/faq_kb\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\n\nvar port = process.env.PORT || '3000';\n\nlet TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/ext/\";;\nif (process.env.TILEBOT_ENDPOINT) {\n    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/ext/\"\n}\n\nwinston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\n/**\n * process.env.API_ENDPOINT --> Internal url of the server inside the cluster\n * process.env.API_URL      --> External/Public server url\n */\nconst apiUrl = process.env.API_ENDPOINT || process.env.API_URL || configGlobal.apiUrl;\nwinston.info('Rasa apiUrl: '+ apiUrl);\n\nconst tybot = require(\"@tiledesk/tiledesk-tybot-connector\");\n\n\nclass Listener {\n\n    listen(config) {\n\n        winston.info('Tilebot Listener listen');\n        winston.debug(\"Tilebot config databaseUri: \" + config.databaseUri);  \n        \n\n        var that = this;\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \"+ port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\"\n        winston.debug(\"Redis host: \"+ host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \"+ password);\n\n        // console.log(\"Using: REDIS_HOST:\", process.env.CACHE_REDIS_HOST);\n        // console.log(\"Using: REDIS_PORT\", process.env.CACHE_REDIS_PORT);\n        // console.log(\"Using: REDIS_PASSWORD\", process.env.CACHE_REDIS_PASSWORD);\n        tybot.startApp(\n            {              \n                MONGODB_URI: config.databaseUri,\n                API_ENDPOINT: apiUrl,\n                REDIS_HOST: host,\n                REDIS_PORT: port,\n                REDIS_PASSWORD: password,\n                CACHE_ENABLED: process.env.CACHE_ENABLED,\n                log: process.env.TILEBOT_LOG\n            }, () => {\n                winston.info(\"TileBot proxy server successfully started.\");                 \n            }\n        );\n\n\n        botEvent.on('faqbot.create', function(bot) {\n            if (TILEBOT_ENDPOINT) {\n\n                winston.debug('bot.type:'+bot.type); \n                if (bot.type===\"tilebot\") {\n\n                    winston.debug('qui.type:'+bot.type); \n\n\n                    Faq_kb.findByIdAndUpdate(bot.id, {\"url\":TILEBOT_ENDPOINT+bot.id}, { new: true, upsert: true }, function (err, savedFaq_kb) {\n\n                    // bot.save(function (err, savedFaq_kb) {\n                        if (err) {\n                         return winston.error('error saving faqkb tilebot ', err)\n                        }\n                        botEvent.emit(\"faqbot.update\",savedFaq_kb); //cache invalidation\n                        winston.verbose('Saved faqkb tilebot', savedFaq_kb.toObject())      \n                    });\n                }\n            }\n        });\n        \n    }\n\n}\n\nvar listener = new Listener();\n\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/trigger/default.js",
    "content": "var Trigger = require('./models/trigger');\n\n\nvar defTrigger = {};\nvar defTriggerObj = {};\n       \n\n\nvar tNewConversationObj = {\nname: 'New Conversation',\ndescription: 'Create a temporary chat when new conversation button is pressed.',\ntrigger: {key:'event.emit',name:'Event emit event', description: 'Standard event emit event'},\nconditions:{ all: [{key:'event.name',fact: 'json',path: 'name', operator:'equal', value: 'new_conversation'}]},\nactions: [{key:'request.create', parameters: {departmentid: 'default', text:\"welcome:tdk_req_status_hidden\"}}], \nenabled: true,\ncode: 's_new_conversation_01',\ntype: 'internal',\nversion: 1,\ncreatedBy: 'system',\nupdatedBy: 'system'\n};\n\nvar tNewConversation = new Trigger(tNewConversationObj);          \n\ndefTrigger['s_new_conversation_01'] = tNewConversation;\ndefTriggerObj['s_new_conversation_01'] = tNewConversationObj;\n\n\n\n\n\nvar tWelcomeOnlineObj = \n{\n    name: 'Online Welcome Greeting',\n    description: 'Send a welcome message if there are online agents to the visitor that create a chat.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false},\n                    {key:'request.snapshot.availableAgentsCount',fact: 'json',path: 'snapshot.availableAgentsCount', operator:'greaterThan', value: 0},\n                    {key:'request.isOpen',fact: 'json',path: 'isOpen', operator:'equal', value: true},\n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'welcome'},\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50}\n                ]},\n    actions: [{key:'message.send', parameters: {text:\"${LABEL_FIRST_MSG}\"}}], \n    enabled:true,\n    code: 's_online_welcome_01',\n    type: 'internal',\n    version: 3,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tWelcomeOnline = new Trigger(tWelcomeOnlineObj);          \n\ndefTrigger['s_online_welcome_01'] = tWelcomeOnline;\ndefTriggerObj['s_online_welcome_01'] = tWelcomeOnlineObj\n\n\n\n\nvar tWelcomeOfflineObj = {\n    name: 'Offline Welcome Greeting',\n    description: 'Send a welcome message if there aren\\'t online agents to the visitor that create a chat.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false},\n                    {key:'request.snapshot.availableAgentsCount',fact: 'json',path: 'snapshot.availableAgentsCount', operator:'equal', value: 0},\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50},\n                    {key:'request.isOpen',fact: 'json',path: 'isOpen', operator:'equal', value: true},\n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'welcome'}\n                ]},\n    actions: [{key:'message.send', parameters: {text:\"${LABEL_FIRST_MSG_NO_AGENTS}\"}}], \n    enabled:true,\n    code: 's_offline_welcome_01',\n    type: 'internal',\n    version: 3,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\nvar tWelcomeOffline = new Trigger(tWelcomeOfflineObj);      \n\ndefTrigger['s_offline_welcome_01'] = tWelcomeOffline;\ndefTriggerObj['s_offline_welcome_01'] = tWelcomeOfflineObj;\n\n\n\nvar tWelcomeClosedOperatingHoursObj = {\n    name: 'Office Closed Notice',\n    description: 'Send an office closed message if a visitor asks for support outside of the operating hours.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false},\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50},\n                    {key:'request.isOpen',fact: 'json',path: 'isOpen', operator:'equal', value: false},\n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'welcome'}\n                ]},\n    actions: [{key:'message.send', parameters: {text:\"${LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED}\"}}], \n    enabled:true,\n    code: 's_closed_operating_hours_01',\n    type: 'internal',\n    version: 2,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\nvar tWelcomeClosedOperatingHours = new Trigger(tWelcomeClosedOperatingHoursObj);      \n\ndefTrigger['s_closed_operating_hours_01'] = tWelcomeClosedOperatingHours;\ndefTriggerObj['s_closed_operating_hours_01'] = tWelcomeClosedOperatingHoursObj;\n\n\n\n\n\n\nvar tInviteBotObj = {\n    name: 'Invite Bot',\n    description: 'Invite if available the department bot to the temporary chat and start it.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50},\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: true},\n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'welcome'}\n                ]},\n    actions: [{key:'request.department.bot.launch', parameters: {text:\"/start\"}}], \n    enabled:true,\n    code: 's_invite_bot_01',\n    type: 'internal',\n    version: 2,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tInviteBot = new Trigger(tInviteBotObj);          \n\ndefTrigger['s_invite_bot_01'] = tInviteBot;\ndefTriggerObj['s_invite_bot_01'] = tInviteBotObj;\n\n\n\n\n\nvar tCheckoutPageObj = {\n    name: 'Checkout Page',\n    description: 'Reduce cart abandonment by engaging customer that are on the checkout page.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:\"request.sourcePageUrl\", fact:\"json\", path:\"sourcePage\", operator:\"in\", value:\"/checkout.html\"},\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false},\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50}\n                ]},\n    actions: [{key:'message.send', parameters: {text:\"Hey, do you need help to complete the checkout?\"}}], \n    enabled:false,\n    code: 's_checkout_page_01',\n    version: 1,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tCheckoutPage = new Trigger(tCheckoutPageObj);          \n\ndefTrigger['s_checkout_page_01'] = tCheckoutPage;\ndefTriggerObj['s_checkout_page_01'] = tCheckoutPageObj;\n\n\nvar tAuthStateChangeProactiveGreetingObj = {\n    name: 'New visitor login',\n    description: 'Create a temporary chat when a new visitor signin.',\n    trigger: {key:'event.emit',name:'Event emit event', description: 'Standard event emit event'},\n    conditions:{ all: [\n                        {key:'event.name',fact: 'json',path: 'name', operator:'equal', value: 'auth_state_changed'},\n                        {key: 'event.attributes.code', fact: 'json', path: 'attributes.event', operator:'equal', value : 201}\n                    ]},\n\n    actions: [{key:'request.create', parameters: {departmentid: 'default', text:\"callout:tdk_req_status_hidden\"}}], \n    enabled: true,\n    code: 's_new_login_01',\n    type: 'internal',\n    version: 1,\n    createdBy: 'system',\n    updatedBy: 'system'\n    };\n    \n    var tAuthStateChangeProactiveGreeting = new Trigger(tAuthStateChangeProactiveGreetingObj);          \n    \n    defTrigger['s_new_login_01'] = tAuthStateChangeProactiveGreeting;\n    defTriggerObj['s_new_login_01'] = tAuthStateChangeProactiveGreetingObj;\n\n\n\nvar tInviteProactiveGreetingBotObj = {\n    name: 'Invite Proactive Greeting Bot',\n    description: 'Invite if available the department bot to proactively greet the temporary chat and start it.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50},\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: true},\n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'callout'}\n                ]},\n    actions: [{key:'request.department.bot.launch', parameters: {text:\"/start\"}}], \n    enabled:true,\n    code: 's_invite_proactive_greeting_bot_01',\n    type: 'internal',\n    version: 2,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tInviteProactiveGreetingBot = new Trigger(tInviteProactiveGreetingBotObj);          \n\ndefTrigger['s_invite_proactive_greeting_bot_01'] = tInviteProactiveGreetingBot;\ndefTriggerObj['s_invite_proactive_greeting_bot_01'] = tInviteProactiveGreetingBotObj;\n\n\n\n\n\nvar tProactiveGreetingMessageObj = \n{\n    name: 'Proactive Greeting',\n    description: 'Send a proactive message when a new user signin.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false},                  \n                    {key:'request.first_text',fact: 'json',path: 'first_text', operator:'equal', value: 'callout'},\n                    {key:'request.statusRequestStatus',fact: 'json',path: 'status', operator:'equal', value: 50}\n                ]},\n    actions: [{key:'message.send', parameters: {text:\"${WELLCOME_MSG}\"}}],  \n    enabled:true,\n    code: 's_proactivegreeting_message_01',\n    type: 'internal',\n    version: 1,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tProactiveGreetingMessage = new Trigger(tProactiveGreetingMessageObj);          \n\ndefTrigger['s_proactivegreeting_message_01'] = tProactiveGreetingMessage;\ndefTriggerObj['s_proactivegreeting_message_01'] = tProactiveGreetingMessageObj\n\n\n\n\n\n\n\nvar tTicketingTakingObj = \n{\n    name: 'Ticketing Taking',\n    description: 'Send a taking message when a new ticket is created.',\n    trigger: {key:'request.create',name:'Request create event', description: 'Standard request create event'},\n    conditions:{ all: [\n                    {key:'request.channel.name',fact: 'json',path: 'channel.name', operator:'equal', value: 'email'},\n                    {key:'request.departmentHasBot',fact: 'json',path: 'department.hasBot', operator:'equal', value: false}                    \n                ]},\n    actions: [{key:'message.send', parameters: {text:\"${TICKET_TAKING}\"}}],  \n    enabled:true,\n    code: 's_ticketing_taking_01',\n    type: 'internal',\n    version: 1,\n    createdBy: 'system',\n    updatedBy: 'system'\n}\n\nvar tTicketingTaking = new Trigger(tTicketingTakingObj);          \n\ndefTrigger['s_ticketing_taking_01'] = tTicketingTaking;\ndefTriggerObj['s_ticketing_taking_01'] = tTicketingTakingObj\n\n\nmodule.exports = {defTrigger:defTrigger, defTriggerObj: defTriggerObj};"
  },
  {
    "path": "pubmodules/trigger/event/actionEventEmitter.js",
    "content": "const EventEmitter = require('events');\n\nclass ActionEventEmitter extends EventEmitter {}\n\n\nconst actionEventEmitter = new ActionEventEmitter();\n\n\n\nmodule.exports = actionEventEmitter;\n"
  },
  {
    "path": "pubmodules/trigger/event/flowEventEmitter.js",
    "content": "const EventEmitter = require('events');\n\nclass FlowEventEmitter extends EventEmitter {}\n\n\nconst flowEventEmitter = new FlowEventEmitter();\n\n\n\nmodule.exports = flowEventEmitter;\n"
  },
  {
    "path": "pubmodules/trigger/event/triggerEventEmitter.js",
    "content": "const EventEmitter = require('events');\n\nclass TriggerEventEmitter extends EventEmitter {}\n\n\nconst triggerEventEmitter = new TriggerEventEmitter();\n\n\n\nmodule.exports = triggerEventEmitter;\n"
  },
  {
    "path": "pubmodules/trigger/index.js",
    "content": "const start = require(\"./start\");\nconst triggerRoute = require(\"./triggerRoute\");\nmodule.exports = {start:start,triggerRoute:triggerRoute};"
  },
  {
    "path": "pubmodules/trigger/models/trigger.js",
    "content": "var mongoose = require('mongoose');\nvar Schema = mongoose.Schema;\n// var TriggerEventSchema = require('./triggerEvent').schema;\n\n// var ConditionSchema = require('./triggerCondition').schema;\n// var ActionSchema = require('./triggerAction').schema;\n\n\n\nvar TriggerConditionSchema = new Schema({\n  key: { //ex: request.firsttext\n    type: String,\n    required: true\n  },\n  fact: { //ex: request.firsttext\n    type: String,\n    required: true\n  },\n  path: { \n    type: String,\n    required: false\n  },\n  operator: { //use ref to OperatorSchema\n    type: String,\n    required: true\n  },\n  // type: { unused\n  //   type: String,\n  //   required: false\n  // },\n  value: {\n    type: Object,\n    required: true\n  },\n},{ _id : false });\n\n\nvar ConditionsSchema = new Schema({\n  all: [TriggerConditionSchema],\n  any: [TriggerConditionSchema]\n});\n\n\n\n\nvar ActionSchema = new Schema({\n  key: { //ex: message.send\n      type: String,\n      required: true,\n      index:true\n    },\n  parameters: {\n      type: Object,\n      required: false\n  },\n  // name: {\n  //   type: String,\n  //   required: true\n  // },\n  // description: {\n  //   type: String,\n  //   required: false\n  // },\n\n  script: {  //use ref\n    type: String,\n    required: false\n  },\n});\n\n\n\n\nvar TriggerEventSchema = new Schema({\n  key: { //ex: request.create\n    type: String,\n    required: true,\n    index:true\n  },\n  name: {\n    type: String,\n    required: true\n  },\n  description: {\n    type: String,\n    required: false\n  },\n});\n\n\n\nvar TriggerSchema = new Schema({\n  name: {\n    type: String,\n    required: true\n  },\n  description: {\n    type: String,\n    required: false\n  },\n  delay:  {\n    type: Number,\n    select: true\n  },\n  // triggers: [TriggerSchema],\n  trigger: TriggerEventSchema,\n  conditions: ConditionsSchema,\n  actions: [ActionSchema],\n  enabled: {\n    type:Boolean,\n    required:true,\n    default:false,\n    index:true\n  },\n  id_project: {\n    type: String,\n    required: true,\n    index:true\n  },\n  code: {\n    type: String,\n    select: true\n  },\n  type: {\n    type: String, //internal per new_conversation e invite bot\n    select: true\n  },\n  version: {\n    type: Number,\n    select: true\n  },\n  createdBy: {\n    type: String,\n    required: true\n  },\n  updatedBy: {\n    type: String,\n    required: true\n  }\n}, {\n    timestamps: true\n  }\n);\n\n\n// let query = {id_project: event.id_project, enabled:true, 'trigger.key':eventKey};\nTriggerSchema.index({ id_project: 1, \"trigger.key\":1, enabled:1 }); \n\nmodule.exports = mongoose.model('trigger', TriggerSchema);\n"
  },
  {
    "path": "pubmodules/trigger/rulesTrigger.js",
    "content": "const requestEvent = require('../../event/requestEvent');\nconst messageEvent = require('../../event/messageEvent');\nconst triggerEventEmitter = require('./event/triggerEventEmitter');\n// const event2Event = require('../../../pubmodules/events/event2Event');\nconst eventEvent = require('../../pubmodules/events/eventEvent');\n\nvar Trigger = require('./models/trigger');\nvar winston = require('../../config/winston');\n\nvar Engine = require('@tiledesk/tiledesk-json-rules-engine').Engine;\n\n\n\nvar messageService = require('../../services/messageService');\nvar requestService = require('../../services/requestService');\nvar MessageConstants = require(\"../../models/messageConstants\");\nvar leadService = require('../../services/leadService');\nvar LeadConstants = require('../../models/leadConstants');\nvar operatingHoursService = require(\"../../services/operatingHoursService\");\nvar sendMessageUtil = require(\"../../utils/sendMessageUtil\");\nvar sendEmailUtil = require(\"../../utils/sendEmailUtil\");\nvar cacheUtil = require(\"../../utils/cacheUtil\");\nvar cacheEnabler = require(\"../../services/cacheEnabler\");\nvar UIDGenerator = require(\"../../utils/UIDGenerator\");\nconst RequestConstants = require('../../models/requestConstants');\nvar Bot = require(\"../../models/faq_kb\");\n\nvar request = require('retry-request', {\n  request: require('request')\n});\nconst uuidv4 = require('uuid/v4');\n\nvar jwt = require('jsonwebtoken');\n\nconst port = process.env.PORT || '3000';\nlet TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/ext/\";;\nif (process.env.TILEBOT_ENDPOINT) {\n    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/ext/\"\n}\nwinston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\nclass RulesTrigger {\n\n    constructor() {\n      // this.engine = new Engine();;\n      // this.engine = undefined;\n      this.engines = {};\n    }\n\n    // getEngine() {\n    //   return this.engine;\n    // }\n\n    listen(success, error) {\n        var that = this;\n\n        var enabled = process.env.TRIGGER_ENABLED || \"true\";\n        winston.debug('Trigger enabled:'+enabled);\n\n        if (enabled===\"true\") {\n          winston.debug('Trigger enabled');\n        }else {\n          winston.info('Trigger disabled');\n          return 0;\n        }\n\n\n     setImmediate(() => {\n\n          requestEvent.on('request.support_group.created', function(request) {\n\n            // performance console log\n            // console.log(\"************* request.support_group.created: \"+new Date().toISOString());\n\n            // requestEvent.on('request.create', function(request) {\n            var requestJson = request.toJSON();\n            operatingHoursService.projectIsOpenNow(request.id_project, function (isOpen, err) {       \n              requestJson.isOpen = isOpen;\n              winston.debug('requestJson: ', requestJson);\n              that.exec(requestJson, 'request.create', success, error);\n            });\n          });\n\n\n\n          // da aggiungere in dashboard\n          requestEvent.on('request.participants.join',  function(data) {      \n            let request = data.request;\n            let member = data.member;\n            var requestJson = request.toJSON();\n            operatingHoursService.projectIsOpenNow(request.id_project, function (isOpen, err) {       \n              requestJson.isOpen = isOpen;\n              winston.debug('requestJson: ', requestJson);\n              that.exec(requestJson, 'request.participants.join', success, error);\n            });\n          });\n\n          messageEvent.on('message.create.from.requester', function(message) {\n            winston.debug('message.create.from.requester', message);\n            // aggiungi is open anche a message.create altrimenti isOpen nn va\n            // operatingHoursService.projectIsOpenNow(request.id_project, function (isOpen, err) {   \n            that.exec(message, 'message.create.from.requester', success, error);\n          });\n  \n          messageEvent.on('message.received', function(message) {\n            that.exec(message, 'message.received', success, error);\n          });\n\n          // event2Event.on('*', function(event){\n          //   winston.verbose('event2Event this.event: ' + this.event);\n          //   that.exec(event, this.event, success,error);\n          // });\n\n          eventEvent.on('event.emit', function(event) {\n            winston.debug('eventEvent event.emit', event);\n            that.exec(event, 'event.emit', success,error);\n          });\n\n        \n        \n          this.runAction();\n\n          winston.info('Trigger rules started');\n\n      });\n\n    }\n\n    runAction() {\n\n      triggerEventEmitter.on('message.send', function(eventTrigger) {\n\n        try {\n\n          winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n          var trigger = eventTrigger.trigger;         \n          winston.debug('runAction trigger', trigger.toObject());\n\n\n          var action = eventTrigger.action;\n          winston.debug('runAction action', action.toObject());\n\n          var fullname = action.parameters.fullName || \"BOT\";\n          winston.debug('runAction action fullname: ' + fullname);\n\n          var sender = \"system\";\n          \n          if (action.parameters.sender) {\n            sender = action.parameters.sender;\n          }\n          winston.debug('runAction action sender: ' + sender);\n\n          var text = action.parameters.text;\n          winston.debug('runAction action text: ' + text);\n\n          var attributes = {templateProcessor: true};\n\n          // var attributes = action.parameters.attributes;\n          // winston.debug('runAction action attributes: ' + attributes);\n\n          if (text && text.endsWith(\":tdk_msg_subtype_info\")) {            \n            attributes.subtype = \"info\";\n            //TODO ATTENTION change value by reference text. Text is string so string is passed by value. No problem\n            text = text.replace(':tdk_msg_subtype_info', '');\n            winston.verbose('tdk_msg_subtype_info');\n          }\n        \n\n          var recipient;\n          if (eventTrigger.eventKey==\"request.create\" || eventTrigger.eventKey==\"request.participants.join\") {\n            recipient = eventTrigger.event.request_id;\n\n            // console.log(\"eventTrigger.event\",eventTrigger.event);\n            // console.log(\"eventTrigger.event.id_project\",eventTrigger.event.id_project);\n            \n          }\n          if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n            recipient = eventTrigger.event.recipient;\n          }\n          if (eventTrigger.eventKey==\"event.emit\") {\n            winston.debug('runAction action event.emit: ', eventTrigger.event.toObject());\n\n            //TODO funziona?\n            recipient = eventTrigger.event.project_user.id_user;\n          }\n          \n          winston.debug('runAction action recipient: ' + recipient);\n\n          var id_project = eventTrigger.event.id_project;\n          winston.debug('runAction action id_project: ' + id_project);\n\n            // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes) {\n            // messageService.send(\n            //   sender, \n            //   fullname,                                     \n            //   recipient,\n            //   text,\n            //   id_project,\n            //   null,\n            //   attributes\n            // );\n            \n            // performance console log\n            // console.log(\"************* send message trigger: \"+new Date().toISOString(), text);\n\n            // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) {\n            sendMessageUtil.send(\n              sender, \n              fullname,\n              recipient,\n              text,\n              id_project,\n              null,\n              attributes\n            );\n\n        } catch(e) {\n          winston.error(\"Error runAction\", e);\n        }\n\n        });\n      \n\n\n        triggerEventEmitter.on('email.send', function(eventTrigger) {\n\n          try {\n\n            winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n            var trigger = eventTrigger.trigger;         \n            winston.debug('runAction trigger', trigger.toObject());\n  \n  \n            var action = eventTrigger.action;\n            winston.debug('runAction action', action.toObject());\n  \n\n            var fullname = action.parameters.fullName || \"BOT\";\n            winston.debug('runAction action fullname: ' + fullname);\n  \n            var subject = action.parameters.subject || \"New Email\";                                   \n\n            winston.debug('runAction action subject: ' + subject);\n\n            var sender = \"system\";\n            \n            if (action.parameters.sender) {\n              sender = action.parameters.sender;\n            }\n            winston.debug('runAction action sender: ' + sender);\n  \n            var text = action.parameters.text;\n            winston.debug('runAction action text: ' + text);\n  \n            var attributes = {};\n  \n            // var attributes = action.parameters.attributes;\n            // winston.debug('runAction action attributes: ' + attributes);\n\n  \n            var recipient;\n            if (eventTrigger.eventKey==\"request.create\" || eventTrigger.eventKey==\"request.participants.join\") {\n              recipient = eventTrigger.event.request_id;\n\n              //custom ocf here\n                                                  //prod                                                          //pre\n              // if (eventTrigger.event.id_project ==\"6406e34727b57500120b1bd6\" || eventTrigger.event.id_project == \"642c609f179910002cc56b3e\") {\n              //   // subject = \"Richiesta di supporto #\" + eventTrigger.event.ticket_id;\n              //   subject = \"Segnalazione #\" + eventTrigger.event.ticket_id;\n              //   if (eventTrigger.event.subject) {\n              //     subject = subject + \" - \" + eventTrigger.event.subject;\n              //   } \n              //   // console.log(\"subject\",subject);\n              // }\n\n            }\n            if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n              recipient = eventTrigger.event.recipient;\n            }\n            if (eventTrigger.eventKey==\"event.emit\") {\n              winston.debug('runAction action event.emit: ', eventTrigger.event.toObject());\n  \n              //TODO funziona?\n              recipient = eventTrigger.event.project_user.id_user;\n            }\n\n            // console.log(\"eventTrigger.event\", eventTrigger.event);\n            \n            winston.debug('runAction action recipient: ' + recipient);\n  \n            var id_project = eventTrigger.event.id_project;\n            winston.debug('runAction action id_project: ' + id_project);\n  \n\n            var message = eventTrigger.event;\n            winston.debug('runAction action message: ', message);\n\n            if (eventTrigger.event.request && eventTrigger.event.request.lead && eventTrigger.event.request.lead.email) {\n              var to = eventTrigger.event.request.lead.email;\n              winston.debug('to ' + to);\n  \n              // sendEmailDirect(to, text, id_project, recipient, subject, message)\n              sendEmailUtil.sendEmailDirect(to, text, id_project, recipient, subject, message);              \n            } else {\n              winston.info('email.send trigger. Lead email is undefined ');\n            }\n              \n  \n          } catch(e) {\n            winston.error(\"Error runAction\", e);\n          }\n  \n          });\n        \n  \n\n\n\n      triggerEventEmitter.on('bot.calling', async (eventTrigger) => {\n\n        try {\n\n          winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n          var trigger = eventTrigger.trigger;         \n          winston.debug('runAction trigger', trigger.toObject());\n\n\n          var action = eventTrigger.action;\n          winston.debug('runAction action', action.toObject());\n\n\n          var intentName = action.parameters.intentName;\n          winston.debug('runAction action intentName: ' + intentName);\n        \n\n          var botId = action.parameters.botId;\n          winston.debug('runAction action botId: ' + botId);\n\n\n          var url = TILEBOT_ENDPOINT+botId;\n          if (action.parameters.url) {\n            url = action.parameters.url;\n          }\n          winston.debug('runAction action url: ' + url);\n\n\n          var message = action.parameters.message;\n          winston.debug('runAction action message: ' + message);\n\n          \n\n        \n          if (eventTrigger.eventKey==\"request.create\" || eventTrigger.eventKey==\"request.participants.join\") {\n            // recipient = eventTrigger.event.request_id;\n          }\n          if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n            // recipient = eventTrigger.event.recipient;\n          }\n          if (eventTrigger.eventKey==\"event.emit\") {\n            winston.debug('runAction action event.emit: ', eventTrigger.event.toObject());            \n            // recipient = eventTrigger.event.project_user.id_user;\n          }\n\n          // console.log(\"eventTrigger.event\", eventTrigger.event);\n          \n          var id_project = eventTrigger.event.id_project;\n          winston.debug('runAction action id_project: ' + id_project);\n\n\n          var payload = Object.assign({}, eventTrigger.event);;\n          winston.debug('runAction action payload: ', payload);\n          \n\n          if (message && payload.text) {\n            payload.text = message;\n            winston.debug('forcing payload text to : ' + payload.text);\n          }\n\n          delete payload.request.snapshot\n      \n          var json = {timestamp: Date.now(), payload: payload};\n    \n\n          json[\"hook\"] = trigger;\n\n\n\n\n          var bot = await Bot.findById(botId).select(\"+secret\").exec();\n          winston.debug(\"bot: \", bot);\n\n          var signOptions = {\n            issuer:  'https://tiledesk.com',\n            subject:  'bot',\n            audience:  'https://tiledesk.com/bots/'+bot._id,   \n            jwtid: uuidv4()       \n          };\n\n\n          let botPayload = bot.toObject();    \n          \n          let botSecret = botPayload.secret;\n\n          delete botPayload.secret;\n          delete botPayload.description;\n          delete botPayload.attributes;\n\n          var token = jwt.sign(botPayload, botSecret, signOptions);\n          json[\"token\"] = token;\n\n\n          var webhook_origin = process.env.WEBHOOK_ORIGIN || \"http://localhost:3000\";\n          winston.debug(\"webhook_origin: \"+webhook_origin);\n\n          winston.debug(\"Rules trigger notify json \", json );\n\n          request({\n            url: url,\n            headers: {\n             'Content-Type' : 'application/json', \n             'User-Agent': 'tiledesk-bot',\n             'Origin': webhook_origin\n              //'x-hook-secret': s.secret\n            },\n            json: json,\n            method: 'POST'\n\n          }, function(err, result, json){            \n            winston.verbose(\"SENT notify for bot with url \" + url +  \" with err \" + err);\n            winston.debug(\"SENT notify for bot with url \", result);\n            if (err) {\n              winston.error(\"Error sending notify for bot with url \" + url + \" with err \" + err);\n              //TODO Reply with error\n              // next(err, json);\n            }\n          });\n    \n            \n\n        } catch(e) {\n          winston.error(\"Error runAction\", e);\n        }\n\n        });\n        \n  \n\n\n        triggerEventEmitter.on('request.department.route', function(eventTrigger) {\n\n          try {\n  \n            winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n            var trigger = eventTrigger.trigger;         \n            winston.debug('runAction trigger', trigger.toObject());\n  \n  \n            var action = eventTrigger.action;\n            winston.debug('runAction action', action.toObject());\n    \n                \n            var departmentid = action.parameters.departmentid;\n            winston.debug('runAction action departmentid: ' + departmentid);\n\n\n            var request_id;\n            if (eventTrigger.eventKey==\"request.create\") {\n              request_id = eventTrigger.event.request_id;\n            }\n            if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n              request_id = eventTrigger.event.recipient;\n            }\n            \n            winston.debug('runAction action request_id: ' + request_id);\n\n            var id_project = eventTrigger.event.id_project;\n            winston.debug('runAction action id_project: ' + id_project);\n\n            // route(request_id, departmentid, id_project, nobot) {\n            requestService.route(request_id, departmentid, id_project).catch((err) => {\n              winston.error(\"Error runAction route: \", err);\n            });\n  \n          } catch(e) {\n            winston.error(\"Error runAction\", e);\n          }\n  \n        });\n\n\n\n\n          triggerEventEmitter.on('request.department.route.self', function(eventTrigger) {\n\n            try {\n    \n              winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n              var trigger = eventTrigger.trigger;         \n              winston.debug('runAction trigger', trigger.toObject());\n    \n              var action = eventTrigger.action;\n              winston.debug('runAction action', action.toObject());\n                           \n              var request_id;\n              if (eventTrigger.eventKey==\"request.create\") {\n                request_id = eventTrigger.event.request_id;\n              }\n              if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                request_id = eventTrigger.event.recipient;\n              }\n              \n              winston.debug('runAction action request_id: ' + request_id);\n  \n              var id_project = eventTrigger.event.id_project;\n              winston.debug('runAction action id_project: ' + id_project);\n  \n              // reroute(request_id, id_project, nobot) {\n              requestService.reroute(request_id, id_project).catch((err) => {\n                winston.error(\"Error runAction on reroute\", err);\n              });                           \n    \n            } catch(e) {\n              winston.error(\"Error runAction\", e);\n            }\n    \n            });\n\n\n\n\n\n            triggerEventEmitter.on('request.status.update', function(eventTrigger) {\n\n              try {\n      \n                winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                var trigger = eventTrigger.trigger;         \n                winston.debug('runAction trigger', trigger.toObject());\n      \n                var action = eventTrigger.action;\n                winston.debug('runAction action', action.toObject());\n                       \n                var newstatus = action.parameters.status;\n                winston.debug('runAction action newstatus: ' + newstatus);\n\n                \n                var request_id;\n                if (eventTrigger.eventKey==\"request.create\") {\n                  request_id = eventTrigger.event.request_id;\n                }\n                if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                  request_id = eventTrigger.event.recipient;\n                }\n                \n                winston.debug('runAction action request_id: ' + request_id);\n    \n                var id_project = eventTrigger.event.id_project;\n                winston.debug('runAction action id_project: ' + id_project);\n    \n                // changeStatusByRequestId(request_id, id_project, newstatus) {\n                requestService.changeStatusByRequestId(request_id, id_project, newstatus);                      \n      \n              } catch(e) {\n                winston.error(\"Error runAction\", e);\n              }\n      \n              });\n\n\n\n\n\n\n              triggerEventEmitter.on('request.close', function(eventTrigger) {\n\n                try {\n        \n                  winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                  var trigger = eventTrigger.trigger;         \n                  winston.debug('runAction trigger', trigger.toObject());\n        \n                  var action = eventTrigger.action;\n                  winston.debug('runAction action', action.toObject());\n                                             \n                      \n                  var request_id;\n                  if (eventTrigger.eventKey==\"request.create\") {\n                    request_id = eventTrigger.event.request_id;\n                  }\n                  if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                    request_id = eventTrigger.event.recipient;\n                  }\n                  \n                  winston.debug('runAction action request_id: ' + request_id);\n      \n                  var id_project = eventTrigger.event.id_project;\n                  winston.debug('runAction action id_project: ' + id_project);\n      \n                  // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by)\n                  const closed_by = \"_trigger\";\n                  requestService.closeRequestByRequestId(request_id, id_project, false, true, closed_by).catch((err) => {\n                    winston.error(\"(RulesTrigger) closeRequestByRequestId error\", err);\n                  });\n                                      \n                } catch(e) {\n                  winston.error(\"Error runAction\", e);\n                }\n        \n                });\n\n\n\n                triggerEventEmitter.on('request.reopen', function(eventTrigger) {\n\n                  try {\n          \n                    winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                    var trigger = eventTrigger.trigger;         \n                    winston.debug('runAction trigger', trigger.toObject());\n          \n                    var action = eventTrigger.action;\n                    winston.debug('runAction action', action.toObject());\n                                               \n                        \n                    var request_id;\n                    if (eventTrigger.eventKey==\"request.create\") {\n                      request_id = eventTrigger.event.request_id;\n                    }\n                    if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                      request_id = eventTrigger.event.recipient;\n                    }\n                    \n                    winston.debug('runAction action request_id: ' + request_id);\n        \n                    var id_project = eventTrigger.event.id_project;\n                    winston.debug('runAction action id_project: ' + id_project);\n        \n                    //   reopenRequestByRequestId(request_id, id_project) {\n                    requestService.reopenRequestByRequestId(request_id, id_project).catch((err) => {\n                      winston.error(\"(RulesTrigger) reopenRequestByRequestId error\", err);\n                    });                    \n          \n                  } catch(e) {\n                    winston.error(\"Error runAction\", e);\n                  }\n          \n             });\n\n\n\n             triggerEventEmitter.on('request.participants.join', function(eventTrigger) {\n\n              try {\n      \n                winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                var trigger = eventTrigger.trigger;         \n                winston.debug('runAction trigger', trigger.toObject());\n      \n                var action = eventTrigger.action;\n                winston.debug('runAction action', action.toObject());\n                             \n                // console.log(\"actionaction\",action);\n\n                var member = action.parameters.member;\n                winston.debug('runAction action member: ' + member);\n\n                var request_id;\n                if (eventTrigger.eventKey==\"request.create\") {\n                  request_id = eventTrigger.event.request_id;\n                }\n                if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                  request_id = eventTrigger.event.recipient;\n                }\n                \n                winston.debug('runAction action request_id: ' + request_id);\n    \n                var id_project = eventTrigger.event.id_project;\n                winston.debug('runAction action id_project: ' + id_project);\n    \n                //     addParticipantByRequestId(request_id, id_project, member) {\n                requestService.addParticipantByRequestId(request_id, id_project, member).catch((err) => {\n                  winston.error(\"(RulesTrigger) addParticipantByRequestId error\", err);\n                });\n        \n                       \n              } catch(e) {\n                winston.error(\"Error runAction\", e);\n              }\n      \n         });\n\n\n\n\n\n\n\n         triggerEventEmitter.on('request.department.bot.launch', function(eventTrigger) {\n\n            try {\n    \n              winston.debug('triggerEventEmitter eventTrigger:', eventTrigger);\n\n\n              winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n              var trigger = eventTrigger.trigger;         \n              winston.debug('runAction trigger', trigger.toObject());\n    \n              var action = eventTrigger.action;\n              winston.debug('runAction action', action.toObject());\n                           \n              var request_id;\n              if (eventTrigger.eventKey==\"request.create\") {\n                request_id = eventTrigger.event.request_id;\n              }\n              if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                request_id = eventTrigger.event.recipient;\n              }\n              \n              winston.debug('runAction action request_id: ' + request_id);\n  \n              var id_project = eventTrigger.event.id_project;\n              winston.debug('runAction action id_project: ' + id_project);\n  \n\n\n              var startText = \"/start\";\n              // var startText = \"\\\\start\";\n              if (action.parameters && action.parameters.text) {\n                startText = action.parameters.text;\n              }\n              winston.debug('runAction action startText: ' + startText);\n\n\n              // reroute(request_id, id_project, nobot) {\n              requestService.reroute(request_id, id_project).then(function(request) {\n\n                winston.verbose('request.department.bot.launch action reroute request_id: ' + request_id);\n\n                // rendi dinamico /start\n                messageService.send(\n                  'system', \n                  'Bot',                                     \n                  request_id,\n                  startText, // /start controlla se chatbot nuovo manda /start altrimenti per i vecchi \\start\n                  id_project,\n                  null,\n                  {subtype:'info', updateconversation : false}\n                );\n    \n                  // TODO Add typing? \n              }).catch((err) => {\n                winston.error(\"Error runAction on reroute\", err);\n              })                       \n    \n\n            } catch(e) {\n              winston.error(\"Error runAction\", e);\n            }\n    \n            });\n\n\n\n\n\n         \n        \n            triggerEventEmitter.on('request.bot.launch', function(eventTrigger) {\n\n              try {\n      \n                winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                var trigger = eventTrigger.trigger;         \n                winston.debug('runAction trigger', trigger.toObject());\n      \n                var action = eventTrigger.action;\n                winston.debug('runAction action', action.toObject());\n                             \n                var request_id;\n                if (eventTrigger.eventKey==\"request.create\") {\n                  request_id = eventTrigger.event.request_id;\n                }\n                if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                  request_id = eventTrigger.event.recipient;\n                }\n                winston.debug('runAction action request_id: ' + request_id);\n\n\n                var member = action.parameters.member;\n                winston.debug('runAction action member: ' + member);\n\n    \n                var id_project = eventTrigger.event.id_project;\n                winston.debug('runAction action id_project: ' + id_project);\n    \n                 requestService.addParticipantByRequestId(request_id, id_project, member).then(function(request) {\n  \n                  winston.verbose('request.bot.launch action request_id: ' + request_id);\n  \n                  // rendi dinamico /start\n                  messageService.send(\n                    'system', \n                    'Bot',                                     \n                    request_id,\n                    '/start', // TODO CHANGE TO / start\n                    id_project,\n                    null,\n                    {subtype:'info', updateconversation : false}\n                  );\n      \n                    // TODO Add typing? \n                })                       \n      \n  \n              } catch(e) {\n                winston.error(\"Error runAction\", e);\n              }\n      \n              });\n\n              \n\n          triggerEventEmitter.on('request.tags.add', function(eventTrigger) {\n\n              try {\n      \n                winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n                var trigger = eventTrigger.trigger;         \n                winston.debug('runAction trigger', trigger.toObject());\n      \n                var action = eventTrigger.action;\n                winston.debug('runAction action', action.toObject());\n                             \n                // console.log(\"actionaction\",action);\n\n                var tag = action.parameters.tag;\n                winston.debug('runAction action tag: ' + tag);\n\n                var request_id;\n                if (eventTrigger.eventKey==\"request.create\") {\n                  request_id = eventTrigger.event.request_id;\n                }\n                if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n                  request_id = eventTrigger.event.recipient;\n                }\n                \n                winston.debug('runAction action request_id: ' + request_id);\n    \n                var id_project = eventTrigger.event.id_project;\n                winston.debug('runAction action id_project: ' + id_project);\n    \n                // addTagByRequestId(request_id, id_project, tag) {\n                requestService.addTagByRequestId(request_id, id_project, {tag:tag}).catch((err) => {\n                  winston.error(\"(RulesTrigger) addTagByRequestId error\", err);\n                });\n        \n                       \n              } catch(e) {\n                winston.error(\"Error runAction\", e);\n              }\n      \n         });\n\n\n\n\n\n         triggerEventEmitter.on('request.participants.leave', function(eventTrigger) {\n\n          try {\n  \n            winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n            var trigger = eventTrigger.trigger;         \n            winston.debug('runAction trigger', trigger.toObject());\n  \n            var action = eventTrigger.action;\n            winston.debug('runAction action', action.toObject());\n\n            var member = action.parameters.member;\n            winston.debug('runAction action member: ' + member);\n\n            var request_id;\n            if (eventTrigger.eventKey==\"request.create\") {\n              request_id = eventTrigger.event.request_id;\n            }\n            if (eventTrigger.eventKey==\"message.create.from.requester\" || eventTrigger.eventKey==\"message.received\") {\n              request_id = eventTrigger.event.recipient;\n            }\n            \n            winston.debug('runAction action request_id: ' + request_id);\n\n            var id_project = eventTrigger.event.id_project;\n            winston.debug('runAction action id_project: ' + id_project);\n\n            //     removeParticipantByRequestId(request_id, id_project, member) {\n            requestService.removeParticipantByRequestId(request_id, id_project, member).catch((err) => {\n              winston.error(\"(RulesTrigger) removeParticipantByRequestId error\", err)\n            });                      \n  \n          } catch(e) {\n            winston.error(\"Error runAction\", e);\n          }\n  \n     });\n\n      \n\n     triggerEventEmitter.on('request.create', function(eventTrigger) {\n\n      try {\n\n          winston.debug('runAction eventTrigger.eventSuccess:', eventTrigger.eventSuccess);\n\n          var trigger = eventTrigger.trigger;         \n          winston.debug('runAction trigger', trigger.toObject());\n\n          var action = eventTrigger.action;\n          winston.debug('runAction action', action.toObject());\n\n\n          var text = action.parameters.text;\n          winston.debug('runAction action text: ' + text);\n\n          var subtype = action.parameters.subtype;\n          winston.debug('runAction action subtype: ' + subtype);;\n\n          var status = action.parameters.status;\n          winston.debug('runAction action status: ' + status);\n\n\n          var preflight = action.parameters.preflight;\n          winston.debug('runAction action preflight: ' + preflight);\n\n          if (text && text.indexOf(\":tdk_msg_subtype_info\")>-1) {\n          //TODO ATTENTION change value by reference text subtype. Text is string so string is passed by value. No problem\n\n            subtype = \"info\";\n            text = text.replace(':tdk_msg_subtype_info', '');\n            winston.debug('tdk_msg_subtype_info');\n          }\n\n          if (text && text.indexOf(\":tdk_req_status_hidden\")>-1) {\n\n            //TODO ATTENTION change value by reference status preflight text. Text is string so string is passed by value. No problem\n            status = 50;\n            preflight = true;\n            text = text.replace(':tdk_req_status_hidden', '');\n            winston.debug('tdk_req_status_hidden');\n          }\n\n          var type = action.parameters.type;\n          winston.debug('runAction action type: ' + type);\n\n        \n\n         \n\n          var departmentid = action.parameters.departmentid;\n          winston.debug('runAction action departmentid: ' + departmentid);\n\n          var participants = action.parameters.participants;\n          winston.debug('runAction action participants: ' + participants);\n          // var attributes = action.parameters.attributes;\n          // winston.debug('runAction action attributes: ' + attributes);\n\n          \n\n          var request_id = 'support-group-'+id_project+\"-\"+UIDGenerator.generate();\n          var id_user;\n          var fullname;\n          var email;\n          var attributes = {};\n\n          \n\n          var sourcePage;\n          var language;\n          var userAgent;\n          \n          var userObj = undefined;\n\n\n          if (eventTrigger.eventKey==\"event.emit\") {\n              winston.verbose('runAction action event.emit: ', eventTrigger.event);\n              //  winston.debug('runAction action event.emit: ', eventTrigger.event.toObject());\n\n              // if (eventTrigger.event.project_user && \n              //   eventTrigger.event.project_user.uuid_user &&\n              //   eventTrigger.event.project_user.uuid_user) {\n                  id_user = eventTrigger.event.project_user.uuid_user;\n\n                  if (eventTrigger.event.user) {\n                    \n                    winston.verbose('eventTrigger.event.user: ', eventTrigger.event.user);\n\n                   \n                    if (eventTrigger.event.user.toObject) {\n                      userObj = eventTrigger.event.user.toObject();\n                    }else {\n                      userObj = eventTrigger.event.user;\n                    }\n                    //TODO ATTENTION change value by reference userObj.password. This is a problem for trigger. Resolve it\n                    delete userObj.password;\n\n\n                    if (eventTrigger.event.user.fullName) {\n                      fullname = eventTrigger.event.user.fullName;\n                      winston.verbose('fullname: '+ fullname);\n                    }\n\n                    if (eventTrigger.event.user.email) {\n                      email = eventTrigger.event.user.email;\n                      winston.verbose('email: '+ email);\n                    }\n\n                  }\n                \n                // }\n              \n\n            // if (attributes && attributes.id_user) {\n            //   id_user = attributes.id_user;\n            // }\n\n            if (eventTrigger.event.attributes) {\n              var eventAttributes = eventTrigger.event.attributes;\n\n              if (eventAttributes.request_id) {\n                request_id = eventAttributes.request_id;\n              }            \n              if (eventAttributes.id_user) {\n                id_user = eventAttributes.id_user;\n              }\n              if (eventAttributes.fullname) {\n                fullname = eventAttributes.fullname;\n              }\n              if (eventAttributes.email) {\n                email = eventAttributes.email;\n              }\n             \n              if (eventAttributes.language) {\n                language = eventAttributes.language;\n              }\n             \n              if (eventAttributes.department) { //TODO Dario change to departmentId\n                departmentid = eventAttributes.department;\n              }\n\n             \n              \n              if (eventAttributes.text) {\n                text = eventAttributes.text;\n              }\n\n               // console.log(\"eventAttributes.participants.length\"+ eventAttributes.participants.length);\n               if (eventAttributes.participants && eventAttributes.participants.length>0) { \n                participants = eventAttributes.participants;\n                if (participants[0].indexOf(\"bot_\")>-1) {\n                  // TODO CHANGE TO /start\n                  text = \"/start\";  //if participants is passed than the bot reply to the first message \"welcome\" so I changed \"welcome\" with \"\\start\"\n                }              \n                // status = RequestConstants.ASSIGNED;\n                // console.log(\"eventAttributes.participants\",eventAttributes.participants);\n              }\n\n              // console.log(\"text\", text);\n\n              if (eventAttributes.status) {\n                status = eventAttributes.status;\n              }\n\n              if (eventAttributes.subtype) {\n                subtype = eventAttributes.subtype;\n              }\n\n\n\n\n            \n\n              if (eventAttributes.attributes) {\n                attributes = eventAttributes.attributes;\n\n\n\n                if (eventAttributes.attributes.client) {\n                  userAgent = eventAttributes.attributes.client;  //the widget pass client parameter and not userAgent\n                }\n                if (eventAttributes.attributes.sourcePage) {\n                  sourcePage = eventAttributes.attributes.sourcePage;\n                }\n\n              }\n\n              \n\n\n          }\n\n\n\n          if (subtype) {\n            attributes.subtype = subtype;\n          }\n\n          if (userObj) {\n            attributes.decoded_jwt = userObj;\n          }           \n            \n\n          }\n\n          winston.debug('runAction action id_user:'+id_user); \n          \n          \n\n          var id_project = eventTrigger.event.id_project;\n          winston.debug('runAction action id_project: ' + id_project);\n\n            \n              // createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes, status) \n            leadService.createIfNotExistsWithLeadId(id_user, fullname , email, id_project, null, attributes,  LeadConstants.TEMP)\n              .then(function(createdLead) {\n                 \n\n             \n\n\n                // return Project_user.findOne(queryProjectUser, function (err, project_user) {\n\n                //   var project_user_id = null; \n\n                //   if (err) {\n                //     winston.error(\"Error getting the project_user_id\", err);\n                //   }\n\n                //   if (project_user) {\n                //     winston.verbose(\"project_user\", project_user);\n                //     project_user_id = project_user.id;\n                //     winston.verbose(\"project_user_id: \" + project_user_id);\n                //   }\n\n\n                var puser = eventTrigger.event.project_user;\n                var project_user_id = puser._id;   //questo è null se nn specifico come trigger un event\n                winston.verbose(\"project_user_id: \" + project_user_id);\n\n\n                // qui c'è errore c21\n                        // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, \n                        // language, userAgent, status, createdBy, attributes, subject, preflight) {\n\n                          // return requestService.createWithIdAndRequester(request_id, project_user_id, createdLead._id, id_project, \n                          //   text, departmentid, sourcePage, \n                          //   language, userAgent, status, id_user, attributes, undefined, preflight).then(function (savedRequest) {\n\n\n                    var new_request = {\n                      request_id: request_id, project_user_id: project_user_id, lead_id: createdLead._id, id_project: id_project,\n                      first_text: text, participants: participants, departmentid: departmentid, sourcePage: sourcePage,\n                      language: language, userAgent: userAgent, status: status, createdBy: id_user,\n                      attributes: attributes, subject: undefined, preflight: preflight, channel: undefined, location: undefined,\n                      lead: createdLead, requester: puser\n                    };\n      \n\n                    return requestService.create(new_request).then(function (savedRequest) {                   \n\n                        if (attributes) {\n                          attributes.sendnotification = false; //  sembra nn funzionae\n                        }\n                        \n                        var senderFullname = fullname || 'Guest'; // guest_here\n\n                        // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n                        return messageService.create( id_user, senderFullname , savedRequest.request_id, text, id_project, id_user,  MessageConstants.CHAT_MESSAGE_STATUS.SENDING, attributes, type, eventTrigger.event.metadata, language).then(function(savedMessage) {\n                          return savedMessage;\n                        });\n                      }).catch(function (err) {\n                        winston.error(\"Error trigger requestService.create\", err);\n                      });\n                  });\n            // });\n\n\n    \n      } catch(e) {\n        winston.error(\"Error runAction\", e);\n      }\n\n      });\n\n\n\n    }\n\n    delayedFunction(action,triggerEvent, waitTime) {\n      setTimeout(function() {\n        triggerEvent.action = action;\n        // console.log(\"setTimeout\",action.key, triggerEvent);\n        triggerEventEmitter.emit(action.key,triggerEvent );\n      }, waitTime);\n    }\n\n    createEngine() {\n\n    }\n\n    exec(event, eventKey,successCall,errorCall) {\n      var that = this;\n        // winston.verbose('this', this);\n\n        // var eventKey = Object.keys(this._events);\n        // var eventKey = Object.values(this._events);\n        // winston.verbose('eventKey: ',eventKey);\n\n        // winston.verbose('this', JSON.stringify(this));\n        setImmediate(() => {\n\n            \n              winston.debug('event', event);\n              winston.debug('successCall', successCall);\n              winston.debug('trigger event', event);\n            \n            let query = {id_project: event.id_project, enabled:true, 'trigger.key':eventKey};\n            \n\n            winston.debug('trigger query', query);\n\n            let q = Trigger.find(query);\n\n            if (cacheEnabler.trigger) {\n              q.cache(cacheUtil.longTTL, event.id_project+\":triggers:trigger.key:\"+eventKey) //CACHE_TRIGGER\n              winston.debug('trigger cache enabled');\n\n            }\n\n            q.exec(function(err, triggers) {\n              \n              \n                if (err) {\n                    winston.error('Error gettting bots ', err);\n                    return 0;\n                }\n                if (!triggers || triggers.length==0) {\n                  winston.debug('No trigger found');\n                  return 0;\n                }\n\n                winston.debug('active triggers found', triggers);\n\n\n                // var engineExists = that.engines.hasOwnProperty(event.id_project);\n                // // var engineExists = that.engines.hasOwnProperty(event.id_project+\"-\"+eventKey);\n                // winston.verbose(\"engineExists:\"+engineExists);\n\n                // var engine;\n                // if (!engineExists) {\n                //   engine = new Engine();\n                //   that.engines[event.id_project] = engine;\n                //   // that.engines[event.id_project+\"-\"+eventKey] = engine;\n                //   winston.verbose(\"create engine\");\n                // }else {\n                //   engine = that.engines[event.id_project];\n                //   // engine = that.engines[event.id_project+\"-\"+eventKey];\n                //   winston.verbose(\"engine already exists\");\n                //   return 0;\n                // }\n\n                var engine = new Engine();     \n                // winston.verbose(\"create engine\");              \n                // that.engine = new Engine();\n\n\n                \n\n                triggers.forEach(function(trigger) { \n                  winston.debug('trigger', trigger.toObject());\n\n                  var rule = {\n                    conditions: {\n                    },\n                    event: {  // define the event to fire when the conditions evaluate truthy\n                      type: trigger.id,\n                      // type: trigger.actions[0].key,\n                      params: trigger.actions\n                      // params: {id: trigger._id, actionParameters:trigger.actions[0].parameters}\n                      // params: {\n                      //   message: 'Player has fouled out!'\n                      // }\n                    }\n                  };\n\n                  // qui se non imposto condizioni  \n                  // uncaughtException: \"conditions\" root must contain a single instance of \"all\" or \"any\"\n                  // 2022-07-18T08:58:18.589888+00:00 app[web.1]: Error: \"conditions\" root must contain a single instance of \"all\" or \"any\"\n\n                  if (trigger.conditions.all.toObject() && trigger.conditions.all.toObject().length>0) {\n                    rule.conditions.all = trigger.conditions.all.toObject();\n                  }\n\n                  if (trigger.conditions.any.toObject() && trigger.conditions.any.toObject().length>0) {\n                    rule.conditions.any = trigger.conditions.any.toObject();\n                  }\n\n                  \n                  winston.debug('rule', rule);\n\n                    // define a rule for detecting the player has exceeded foul limits.  Foul out any player who:\n                // (has committed 5 fouls AND game is 40 minutes) OR (has committed 6 fouls AND game is 48 minutes)\n                  \n                  engine.addRule(rule);\n                // that.engine.addRule(rule);\n\n                });\n                \n                \n\n\n\n                \n\n\n              \n\n\n                \n                \n                /**\n                 * Define facts the engine will use to evaluate the conditions above.\n                 * Facts may also be loaded asynchronously at runtime; see the advanced example below\n                 */\n                \n\n                // let facts = event.toObject();\n                let facts;\n                if  (event.toJSON) {  //request is mongoose object\n                    facts = event.toJSON();\n                }else {                 //message is plain object because messageEvent replace it\n                    facts = event;\n                }\n                winston.verbose(\"facts\", facts);\n\n                engine.addFact(\"json\", facts)                                       \n\n\n\n                engine.on('success', function(eventSuccess, almanac, ruleResult) {\n                  // info: runAction eventTrigger.eventSuccess: {\"type\":\"request.create\",\"params\":{\"id\":\"5e4a771a248688f8ea55e47a\",\"actionParameters\":{\"fullName\":\"fullName\",\"text\":\"hi\"}}}\n                  winston.debug(\"success eventSuccess\", eventSuccess); \n                  winston.debug(\"success ruleResult\", ruleResult); \n\n                  var triggerEvent = {event: event, eventKey:eventKey , triggers: triggers, ruleResult:requestEvent,eventSuccess:eventSuccess, engine:engine };\n\n                  // info: test success eventTrigger:{\n                    // \"event\":{\"_id\":\"5e4a771a248688f8ea55e47e\",\"name\":\"user.pay\",\"attributes\":{\"attr1\":\"val1\"},\"id_project\":\"5e4a771a248688f8ea55e476\",\"project_user\":{\"_id\":\"5e4a771a248688f8ea55e477\",\"id_project\":\"5e4a771a248688f8ea55e476\",\"id_user\":{\"_id\":\"5e4a771a248688f8ea55e475\",\"email\":\"test-trigger-EventEmit-1581938458317@email.com\",\"firstname\":\"Test Firstname\",\"lastname\":\"Test lastname\",\"createdAt\":\"2020-02-17T11:20:58.326Z\",\"updatedAt\":\"2020-02-17T11:20:58.326Z\",\"__v\":0},\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e4a771a248688f8ea55e475\",\"createdAt\":\"2020-02-17T11:20:58.580Z\",\"updatedAt\":\"2020-02-17T11:20:58.580Z\",\"__v\":0,\"id\":\"5e4a771a248688f8ea55e477\"},\"createdBy\":\"5e4a771a248688f8ea55e475\",\"createdAt\":\"2020-02-17T11:20:58.603Z\",\"updatedAt\":\"2020-02-17T11:20:58.603Z\",\"__v\":0},\n                    // \"eventKey\":\"event.emit\",\n                    // \"triggers\":[{\"enabled\":true,\"_id\":\"5e4a771a248688f8ea55e47a\",\"name\":\"test\",\"description\":\"test desc\",\"id_project\":\"5e4a771a248688f8ea55e476\",\"trigger\":{\"_id\":\"5e4a771a248688f8ea55e47b\",\"key\":\"event.emit\",\"name\":\"event emit event\",\"description\":\"event emit descr\"},\"conditions\":{\"_id\":\"5e4a771a248688f8ea55e47c\",\"all\":[{\"key\":\"event1\",\"fact\":\"json\",\"path\":\"attributes.attr1\",\"operator\":\"equal\",\"value\":\"val1\"}],\"any\":[]},\"actions\":[{\"_id\":\"5e4a771a248688f8ea55e47d\",\"key\":\"request.create\",\"parameters\":{\"fullName\":\"fullName\",\"text\":\"hi\"}}],\"createdBy\":\"5e4a771a248688f8ea55e475\",\"createdAt\":\"2020-02-17T11:20:58.597Z\",\"updatedAt\":\"2020-02-17T11:20:58.597Z\",\"__v\":0}],\n                    // \"ruleResult\":{\"_events\":{\"request.create\":[null,null,null,null,null],\"request.close\":[null,null,null]},\"_eventsCount\":7}\n                    // ,\"eventSuccess\":{\"type\":\"request.create\",\"params\":{\"id\":\"5e4a771a248688f8ea55e47a\",\"actionParameters\":{\"fullName\":\"fullName\",\"text\":\"hi\"}}},\n                    // \"engine\":{\"_events\":{},\"_eventsCount\":2,\"rules\":[\"{\\\"conditions\\\":{\\\"priority\\\":1,\\\"all\\\":[{\\\"operator\\\":\\\"equal\\\",\\\"value\\\":\\\"val1\\\",\\\"fact\\\":\\\"json\\\",\\\"path\\\":\\\"attributes.attr1\\\"}]},\\\"priority\\\":1,\\\"event\\\":{\\\"type\\\":\\\"request.create\\\",\\\"params\\\":{\\\"id\\\":\\\"5e4a771a248688f8ea55e47a\\\",\\\"actionParameters\\\":{\\\"fullName\\\":\\\"fullName\\\",\\\"text\\\":\\\"hi\\\"}}}}\"],\"allowUndefinedFacts\":false,\"operators\":{},\"facts\":{},\"status\":\"RUNNING\",\"prioritizedRules\":[[\"{\\\"conditions\\\":{\\\"priority\\\":1,\\\"all\\\":[{\\\"operator\\\":\\\"equal\\\",\\\"value\\\":\\\"val1\\\",\\\"fact\\\":\\\"json\\\",\\\"path\\\":\\\"attributes.attr1\\\"}]},\\\"priority\\\":1,\\\"event\\\":{\\\"type\\\":\\\"request.create\\\",\\\"params\\\":{\\\"id\\\":\\\"5e4a771a248688f8ea55e47a\\\",\\\"actionParameters\\\":{\\\"fullName\\\":\\\"fullName\\\",\\\"text\\\":\\\"hi\\\"}}}}\"]]}}\n\n\n                    // dopo\n                    //  {\"event\":{\"_id\":\"5e4a7e52f3f18136851535f5\",\"name\":\"user.pay\",\"attributes\":{\"attr1\":\"val1\"},\"id_project\":\"5e4a7e52f3f18136851535ed\",\"project_user\":{\"_id\":\"5e4a7e52f3f18136851535ee\",\"id_project\":\"5e4a7e52f3f18136851535ed\",\"id_user\":{\"_id\":\"5e4a7e52f3f18136851535ec\",\"email\":\"test-trigger-EventEmit-1581940306138@email.com\",\"firstname\":\"Test Firstname\",\"lastname\":\"Test lastname\",\"createdAt\":\"2020-02-17T11:51:46.146Z\",\"updatedAt\":\"2020-02-17T11:51:46.146Z\",\"__v\":0},\"role\":\"owner\",\"user_available\":true,\"createdBy\":\"5e4a7e52f3f18136851535ec\",\"createdAt\":\"2020-02-17T11:51:46.397Z\",\"updatedAt\":\"2020-02-17T11:51:46.397Z\",\"__v\":0,\"id\":\"5e4a7e52f3f18136851535ee\"},\"createdBy\":\"5e4a7e52f3f18136851535ec\",\"createdAt\":\"2020-02-17T11:51:46.422Z\",\"updatedAt\":\"2020-02-17T11:51:46.422Z\",\"__v\":0},\n                    // \"eventKey\":\"event.emit\",\n                    // \"triggers\":[{\"enabled\":true,\"_id\":\"5e4a7e52f3f18136851535f1\",\"name\":\"test\",\"description\":\"test desc\",\"id_project\":\"5e4a7e52f3f18136851535ed\",\"trigger\":{\"_id\":\"5e4a7e52f3f18136851535f2\",\"key\":\"event.emit\",\"name\":\"event emit event\",\"description\":\"event emit descr\"},\"conditions\":{\"_id\":\"5e4a7e52f3f18136851535f3\",\"all\":[{\"key\":\"event1\",\"fact\":\"json\",\"path\":\"attributes.attr1\",\"operator\":\"equal\",\"value\":\"val1\"}],\"any\":[]},\"actions\":[{\"_id\":\"5e4a7e52f3f18136851535f4\",\"key\":\"request.create\",\"parameters\":{\"fullName\":\"fullName\",\"text\":\"hi\"}}],\"createdBy\":\"5e4a7e52f3f18136851535ec\",\"createdAt\":\"2020-02-17T11:51:46.416Z\",\"updatedAt\":\"2020-02-17T11:51:46.416Z\",\"__v\":0}],\n                    // \"ruleResult\":{\"_events\":{\"request.create\":[null,null,null,null,null],\"request.close\":[null,null,null]},\"_eventsCount\":7},\n                    // \"eventSuccess\":{\"type\":\"5e4a7e52f3f18136851535f1\",\"params\":[{\"_id\":\"5e4a7e52f3f18136851535f4\",\"key\":\"request.create\",\"parameters\":{\"fullName\":\"fullName\",\"text\":\"hi\"}}]},\n                    // \"engine\":{\"_events\":{},\"_eventsCount\":2,\"rules\":[\"{\\\"conditions\\\":{\\\"priority\\\":1,\\\"all\\\":[{\\\"operator\\\":\\\"equal\\\",\\\"value\\\":\\\"val1\\\",\\\"fact\\\":\\\"json\\\",\\\"path\\\":\\\"attributes.attr1\\\"}]},\\\"priority\\\":1,\\\"event\\\":{\\\"type\\\":\\\"5e4a7e52f3f18136851535f1\\\",\\\"params\\\":[{\\\"_id\\\":\\\"5e4a7e52f3f18136851535f4\\\",\\\"key\\\":\\\"request.create\\\",\\\"parameters\\\":{\\\"fullName\\\":\\\"fullName\\\",\\\"text\\\":\\\"hi\\\"}}]}}\"],\"allowUndefinedFacts\":false,\"operators\":{},\"facts\":{},\"status\":\"RUNNING\",\"prioritizedRules\":[[\"{\\\"conditions\\\":{\\\"priority\\\":1,\\\"all\\\":[{\\\"operator\\\":\\\"equal\\\",\\\"value\\\":\\\"val1\\\",\\\"fact\\\":\\\"json\\\",\\\"path\\\":\\\"attributes.attr1\\\"}]},\\\"priority\\\":1,\\\"event\\\":{\\\"type\\\":\\\"5e4a7e52f3f18136851535f1\\\",\\\"params\\\":[{\\\"_id\\\":\\\"5e4a7e52f3f18136851535f4\\\",\\\"key\\\":\\\"request.create\\\",\\\"parameters\\\":{\\\"fullName\\\":\\\"fullName\\\",\\\"text\\\":\\\"hi\\\"}}]}}\"]]}}\n                  winston.debug(\"success triggerEvent\", triggerEvent); \n                  \n                  var pickedTrigger = triggers.filter( function (t) {\n                    // winston.verbose(\"t:\"+t._id);\n                    // winston.verbose(\"eventSuccess.type:\"+eventSuccess.type);\n                    if (t.id === eventSuccess.type) {\n                      // winston.verbose(\"uguale\");\n                      return true;\n                    }\n                  });\n                  if (pickedTrigger && pickedTrigger.length>0) {\n                    pickedTrigger = pickedTrigger[0];\n                  }\n\n                  winston.debug(\"pickedTrigger\", pickedTrigger); \n                  triggerEvent.trigger = pickedTrigger;\n\n\n\n                  // shiiiit https://stackoverflow.com/questions/37977602/settimeout-not-working-inside-foreach\n\n                  // var a = [1,2,3]\n                  // // var index = 0;   <- BUGGGGGGGG try it in the browser you must user index of the forEach\n                  // a.forEach(function(e,index,c) {\n                  //   // a.forEach(function(e) {\n                  //   setTimeout(function() {\n                  //     console.log(index, e);\n                  //   },index * 1000);\n                  //   // index++;\n                  // });\n\n\n\n                  // https://coderwall.com/p/_ppzrw/be-careful-with-settimeout-in-loops\n                  pickedTrigger.actions.forEach(function (action, i, collection) {\n                    winston.debug(\"triggerEventEmitter emit: \" + action.key, action);\n                    // triggerEvent.action = action;\n                    var waitTime = 500*i;\n                    // console.log(\"waitTime\",waitTime);\n                    // make timeout of 500 ms \n                    that.delayedFunction(action, triggerEvent, waitTime );\n                    \n                  });\n\n                  // pickedTrigger.actions.forEach(function (action, i, collection) {\n                  //   winston.verbose(\"triggerEventEmitter emit: \" + action.key, action);\n                  //   triggerEvent.action = action;\n                  //   var waitTime = 500*i;\n                  //   console.log(\"waitTime\",waitTime);\n                  //   // make timeout of 500 ms \n                  //   setTimeout(function() {\n                  //     console.log(\"setTimeout\",action.key, triggerEvent);\n                  //     triggerEventEmitter.emit(action.key,triggerEvent );\n                  //   }, waitTime);\n                  //   // i++;\n                  // });\n                  \n\n                  // successCall(eventSuccess.type,triggerEvent);\n                });\n\n                \n\n                engine.on('failure', function(eventFailure, almanac, ruleResult) {\n                  winston.debug(\"failure eventFailure\", eventFailure); \n\n                  var triggerEvent = {event: event, eventKey:eventKey , triggers: triggers, ruleResult:requestEvent,engine:engine };\n                  winston.debug(\"failure triggerEvent\", triggerEvent); \n\n\n                  var pickedTrigger = triggers.filter( function (t) {\n                    // winston.verbose(\"t:\"+t._id);\n                    // winston.verbose(\"eventFailure.type:\"+eventFailure.type);\n                    if (t.id === eventFailure.type) {\n                      // winston.verbose(\"uguale\");\n                      return true;\n                    }\n                  });\n                  if (pickedTrigger && pickedTrigger.length>0) {\n                    pickedTrigger = pickedTrigger[0];\n                  }\n\n                  winston.debug(\"pickedTrigger\", pickedTrigger); \n                  triggerEvent.trigger = pickedTrigger;\n                  pickedTrigger.actions.forEach(function (action) {\n                    winston.debug(\"triggerEventEmitter emit: \" + action.key);\n                    triggerEvent.action = action;\n                    triggerEventEmitter.emit(action.key+\".failure\",triggerEvent );\n                  });\n\n                  // triggerEventEmitter.emit(eventFailure.type+\".failure\", triggerEvent);\n                  // errorCall(eventSuccess.type,triggerEvent);\n                });\n\n\n\n\n\n                // Run the engine to evaluate\n                engine\n                  // .run(facts)\n                  .run()\n                  .then(events => { // run() returns events with truthy conditions\n                    winston.verbose('all rules executed; the following events were triggered: ', events);\n                    engine.stop();\n                    // events.map(event => winston.debug(event.params.message));\n                  })\n                  // .catch () per beccare  uncaughtException: \"conditions\" root must contain a single instance of \"all\" or \"any\"\n                              \n\n\n            });        \n\n        });\n    }\n\n}\n\nvar rulesTrigger = new RulesTrigger();\nmodule.exports = rulesTrigger;\n\n"
  },
  {
    "path": "pubmodules/trigger/start.js",
    "content": "var faqService = require('../../services/faqService');\nvar Trigger = require('./models/trigger');\nvar DefaultTrigger = require('./default');\nvar projectEvent = require('../../event/projectEvent');\nvar winston = require('../../config/winston');\nconst chatbotTypes = require(\"../../models/chatbotTypes\");\nvar rulesTrigger = require('./rulesTrigger');\n\n\nfunction saveTrigger(trigger) {\n    trigger.save(function (err, savedTrigger) { \n        if (err) {\n           return winston.error(\"Error saving trigger for project with id \"+trigger.id_project , err);\n        }\n        return winston.debug(\"Trigger saved for project with id \"+trigger.id_project, saveTrigger);\n    }); \n}\nprojectEvent.on('project.create', async (project) => {\n    setImmediate( async () => {\n\n        let identity_bot = {\n            name: \"Bot\",\n            type: \"identity\",\n            subtype: chatbotTypes.CHATBOT,\n            description: \"An identity bot\"\n        }\n        \n        var botIdentity = await faqService.create(project._id, \"system\", identity_bot);\n        var botIdentityId = \"bot_\"+botIdentity.id;\n        winston.debug(\"botIdentityId:\"+botIdentityId);\n\n\n        winston.debug(\"DefaultTrigger.defTriggerObj.s_new_conversation_01\",DefaultTrigger.defTriggerObj.s_new_conversation_01._id);\n\n        let s_new_conversation_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_new_conversation_01);\n        s_new_conversation_01.id_project = project._id;\n        winston.debug(\"s_new_conversation_01\",s_new_conversation_01);\n        let s_new_conversation_01Trigger = new Trigger(s_new_conversation_01);\n        saveTrigger(s_new_conversation_01Trigger);\n\n\n\n        // let s_proactive_greeting_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_proactive_greeting_01);\n        // s_proactive_greeting_01.id_project = project._id;\n        // let s_proactive_greeting_01Trigger = new Trigger(s_proactive_greeting_01);\n        // saveTrigger(s_proactive_greeting_01Trigger);\n\n\n        let s_online_welcome_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_online_welcome_01);\n        s_online_welcome_01.id_project = project._id;\n        s_online_welcome_01.actions[0].parameters.sender = botIdentityId;\n        let s_online_welcome_01Trigger = new Trigger(s_online_welcome_01);\n        saveTrigger(s_online_welcome_01Trigger);\n\n        let s_offline_welcome_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_offline_welcome_01);\n        s_offline_welcome_01.id_project = project._id;\n        s_offline_welcome_01.actions[0].parameters.sender = botIdentityId;\n        let s_offline_welcome_01Trigger = new Trigger(s_offline_welcome_01);\n        saveTrigger(s_offline_welcome_01Trigger);\n\n\n        let s_closed_operating_hours_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_closed_operating_hours_01);\n        s_closed_operating_hours_01.id_project = project._id;\n        s_closed_operating_hours_01.actions[0].parameters.sender = botIdentityId;\n        let s_closed_operating_hours_01Trigger = new Trigger(s_closed_operating_hours_01);\n        saveTrigger(s_closed_operating_hours_01Trigger);\n\n\n        let s_invite_bot_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_invite_bot_01);\n        s_invite_bot_01.id_project = project._id;\n        let s_invite_bot_01Trigger = new Trigger(s_invite_bot_01);\n        saveTrigger(s_invite_bot_01Trigger);\n\n\n        let s_checkout_page_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_checkout_page_01);\n        s_checkout_page_01.id_project = project._id;\n        s_checkout_page_01.actions[0].parameters.sender = botIdentityId;\n        let s_checkout_page_01Trigger = new Trigger(s_checkout_page_01);\n        saveTrigger(s_checkout_page_01Trigger);\n\n\n        let s_ticketing_taking_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_ticketing_taking_01);\n        s_ticketing_taking_01.id_project = project._id;\n        s_ticketing_taking_01.actions[0].parameters.sender = botIdentityId;\n        let s_ticketing_taking_01Trigger = new Trigger(s_ticketing_taking_01);\n        saveTrigger(s_ticketing_taking_01Trigger);\n\n\n\n//DIABLED\n/*\n        let s_new_login_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_new_login_01);\n        s_new_login_01.id_project = project._id;\n        let s_new_login_01Trigger = new Trigger(s_new_login_01);\n        saveTrigger(s_new_login_01Trigger);\n\n        let s_invite_proactive_greeting_bot_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_invite_proactive_greeting_bot_01);\n        s_invite_proactive_greeting_bot_01.id_project = project._id;\n        let s_invite_proactive_greeting_bot_01Trigger = new Trigger(s_invite_proactive_greeting_bot_01);\n        saveTrigger(s_invite_proactive_greeting_bot_01Trigger);\n        \n        let s_proactivegreeting_message_01 = Object.assign({}, DefaultTrigger.defTriggerObj.s_proactivegreeting_message_01);\n        s_proactivegreeting_message_01.id_project = project._id;\n        s_proactivegreeting_message_01.actions[0].parameters.sender = botIdentityId;\n        let s_proactivegreeting_message_01Trigger = new Trigger(s_proactivegreeting_message_01);\n        saveTrigger(s_proactivegreeting_message_01Trigger);\n*/\n\n        \n        winston.debug(\"triggers created for new project\");\n\n\n\n\n        // cc\n\n    });\n});\n\nrulesTrigger.listen();\n\n\n\n"
  },
  {
    "path": "pubmodules/trigger/triggerRoute.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Trigger = require(\"./models/trigger\");\nvar triggerEventEmitter = require(\"./event/triggerEventEmitter\");\nvar DefaultTrigger = require('./default');\n\nvar winston = require('../../config/winston');\n\n\nwinston.debug(\"trigger route\");\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n\n  var trigger = new Trigger({\n    name: req.body.name,\n    description: req.body.description,\n    id_project: req.projectid,\n    trigger: req.body.trigger,\n    conditions: req.body.conditions,\n    actions: req.body.actions,\n    enabled:true,\n    createdBy: req.user.id,\n    updatedBy: req.user.id\n    });\n\n    trigger.save(function (err, savedTrigger) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n\n    triggerEventEmitter.emit('trigger.create', savedTrigger);\n\n    res.json(savedTrigger);\n  });\n});\n\nrouter.put('/:triggerid', function (req, res) {\n\n  winston.debug(req.body);      \n\n  \n  var update = {};\n  update.name = req.body.name;\n  update.description = req.body.description;\n  update.trigger = req.body.trigger;\n  update.conditions = req.body.conditions;\n\n  update.actions = req.body.actions;\n  update.enabled = req.body.enabled;\n  update.id_project = req.projectid;\n  update.updatedBy = req.user.id;\n\n\n  Trigger.findByIdAndUpdate(req.params.triggerid, update, { new: true, upsert: true }, function (err, updatedTrigger) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    triggerEventEmitter.emit('trigger.update', updatedTrigger);\n\n    res.json(updatedTrigger);\n  });\n});\n\n// curl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/5e9eca85696a55a7eb4615f2/modules/triggers/s_invite_bot_01/reset\n\nrouter.put('/:triggercode/reset', function (req, res) {\n\n  winston.debug(req.body);\n\n  var query = {code:req.params.triggercode, id_project:  req.projectid};\n  winston.verbose(\"query resetting trigger: \" , query);\n\n\n  let triggerObj = Object.assign({}, DefaultTrigger.defTrigger[req.params.triggercode].toJSON());\n  triggerObj.id_project = req.projectid;\n\n  winston.verbose(\"reset triggerObj: \" ,triggerObj);\n\n  let trigger = new Trigger(triggerObj);\n  // var trigger = DefaultTrigger.defTrigger[req.params.triggercode];\n  // winston.verbose(\"trigger: \", trigger.toJSON());\n\n  Trigger.remove(query, function (errOld, triggerOld) {\n    if (errOld) {\n      winston.error('Error deleting trigger ', errOld);\n      // return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n    trigger.save(function (err, updatedTrigger) {\n      if (err) {\n            winston.error('--- > ERROR ', {triggerObj:triggerObj, err: err, query: query});\n            return res.status(500).send({ success: false, msg: 'Error updating object.' });\n          }\n        triggerEventEmitter.emit('trigger.update', updatedTrigger); \n        res.json(updatedTrigger);\n    });\n  });\n\n  \n});\n\n\n\nrouter.delete('/:triggerid', function (req, res) {\n\n  winston.debug(req.body);\n  Trigger.findByIdAndRemove(req.params.triggerid, { new: false}, function (err, trigger) {\n  // Trigger.remove({ _id: req.params.triggerid }, function (err, trigger) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n    triggerEventEmitter.emit('trigger.delete', trigger); \n\n    res.json(trigger);\n  });\n});\n\n\nrouter.get('/:triggerid', function (req, res) {\n\n  winston.debug(req.body);\n\n  Trigger.findById(req.params.triggerid, function (err, trigger) {\n    if (err) {\n      winston.error('--- > ERROR ', err)\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!trigger) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(trigger);\n  });\n});\n\n\n\nrouter.get('/', function (req, res) {\n\n  Trigger.find({ \"id_project\": req.projectid }, function (err, triggers) {\n    if (err) return next(err);\n    res.json(triggers);\n  });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "pubmodules/voice/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst voice = require(\"@tiledesk/tiledesk-vxml-connector\");\nconst voiceRoute = voice.router;\n\n\nmodule.exports = { listener: listener, voiceRoute: voiceRoute }"
  },
  {
    "path": "pubmodules/voice/listener.js",
    "content": "const voice = require(\"@tiledesk/tiledesk-vxml-connector\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\nconst mongoose = require(\"mongoose\");\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('Voice apiUrl: ' + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"Voice Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"Voice config databaseUri: \" + config.databaseUri);\n        }\n\n        var pooling_delay = process.env.BASE_POOLING_DELAY || 250;\n        winston.debug(\"Pooling_delay: \"+ pooling_delay);\n\n        var max_polling_time = process.env.MAX_POOLING_TIME || 30000;\n        winston.debug(\"Pooling_delay: \"+ pooling_delay);\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \"+ port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\"\n        winston.debug(\"Redis host: \"+ host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \"+ password);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME\n        }\n\n        let log = process.env.VOICE_LOG || false\n        winston.debug(\"Voice log: \"+ log);\n\n        const baseUrl = apiUrl + \"/modules/voice\";\n        winston.debug(\"Voice baseUrl: \"+ baseUrl);\n\n        const relativeBaseUrl = process.env.API_ENDPOINT + \"/modules/voice\";\n        winston.debug(\"Voice relativeBaseUrl: \"+ relativeBaseUrl);\n\n        voice.startApp({\n            MONGODB_URI: config.databaseUri,          \n            dbconnection: dbConnection,\n            BASE_URL: baseUrl,\n            RELATIVE_BASE_URL: relativeBaseUrl,\n            REDIS_HOST: host,\n            REDIS_PORT: port,\n            REDIS_PASSWORD: password,\n            BRAND_NAME: brand_name,\n            BASE_POOLING_DELAY: pooling_delay,\n            MAX_POLLING_TIME: max_polling_time,\n            log: log\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Tiledesk Voice Connector proxy server succesfully started.\");\n            } else {\n                winston.info(\"unable to start Tiledesk Voice Connector. \" + err);\n            }\n        })\n\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "pubmodules/voice-twilio/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst twilio_voice = require('@tiledesk/tiledesk-voice-twilio-connector');\nconst voiceTwilioRoute = twilio_voice.router;\n\nmodule.exports = { listener: listener, voiceTwilioRoute: voiceTwilioRoute }"
  },
  {
    "path": "pubmodules/voice-twilio/listener.js",
    "content": "const voice_twilio = require('@tiledesk/tiledesk-voice-twilio-connector');\nlet winston = require('../../config/winston');\nlet configGlobal = require('../../config/global');\nconst mongoose = require('mongoose');\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info(\"TwilioVoice apiUrl: \" + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    async listen(config) {\n        winston.info(\"TwilioVoice Listener listen\");\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \" + port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\";\n        winston.debug(\"Redis host: \" + host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \" + password);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME;\n        }\n\n        let openai_endpoint = process.env.OPENAI_ENDPOINT;\n        winston.debug(\"OpenAI Endpoint: \", openai_endpoint);\n\n        let gpt_key = process.env.GPTKEY;\n\n        let elevenlabs_endpoint = process.env.ELEVENLABS_ENDPOINT || \"https://api.elevenlabs.io\";\n        winston.debug(\"ElevenLabs Endpoint: \", elevenlabs_endpoint);\n\n        const baseUrl = apiUrl + \"/modules/voice-twilio\";\n        winston.debug(\"Voice baseUrl: \"+ baseUrl);\n\n        const relativeBaseUrl = process.env.API_ENDPOINT + \"/modules/voice-twilio\";\n        winston.debug(\"Voice relativeBaseUrl: \"+ relativeBaseUrl);\n\n        let log = process.env.VOICE_TWILIO_LOG || 'error'\n        winston.debug(\"Voice log: \"+ log);\n        \n        try {\n            // startServer is async and returns a Promise (no callback)\n            await voice_twilio.init({\n                MONGODB_URI: config.databaseUri,          \n                dbconnection: dbConnection,\n                BASE_URL: baseUrl,\n                RELATIVE_BASE_URL: relativeBaseUrl,\n                BASE_FILE_URL: apiUrl,                     \n                REDIS_HOST: host,\n                REDIS_PORT: port,\n                REDIS_PASSWORD: password,\n                BRAND_NAME: brand_name,\n                OPENAI_ENDPOINT: openai_endpoint,\n                GPT_KEY: gpt_key,\n                ELEVENLABS_ENDPOINT: elevenlabs_endpoint,\n                VOICE_TWILIO_LOG: log\n            })\n\n            winston.info(\"Tiledesk Twilio Voice Connector proxy server successfully started.\");\n\n        } catch (err) {\n            winston.error(\"Unable to start Tiledesk Twilio Voice Connector. \" + err);\n            throw err; // Re-throw if you want to handle the error upstream\n        }\n        \n    }\n}\n\nlet listener = new Listener();\n\nmodule.exports = listener;\n"
  },
  {
    "path": "pubmodules/whatsapp/index.js",
    "content": "const listener = require(\"./listener\");\n\nconst whatsapp = require(\"@tiledesk/tiledesk-whatsapp-connector\");\nconst whatsappRoute = whatsapp.router;\n\n\nmodule.exports = { listener: listener, whatsappRoute: whatsappRoute }"
  },
  {
    "path": "pubmodules/whatsapp/listener.js",
    "content": "const whatsapp = require(\"@tiledesk/tiledesk-whatsapp-connector\");\nvar winston = require('../../config/winston');\nvar configGlobal = require('../../config/global');\nconst mongoose = require(\"mongoose\");\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nwinston.info('Whatsapp apiUrl: ' + apiUrl);\n\nconst dbConnection = mongoose.connection;\n\nclass Listener {\n\n    listen(config) {\n        winston.info(\"WhatsApp Listener listen\");\n        if (config.databaseUri) {\n            winston.debug(\"whatsapp config databaseUri: \" + config.databaseUri);\n        }\n\n        var port = process.env.CACHE_REDIS_PORT || 6379;\n        winston.debug(\"Redis port: \"+ port);\n\n        var host = process.env.CACHE_REDIS_HOST || \"127.0.0.1\"\n        winston.debug(\"Redis host: \"+ host);\n\n        var password = process.env.CACHE_REDIS_PASSWORD;\n        winston.debug(\"Redis password: \"+ password);\n\n        let graph_url = process.env.META_GRAPH_URL || config.graphUrl || \"https://graph.facebook.com/v14.0/\"\n        winston.debug(\"Whatsapp graph_url: \"+ password);\n\n        let fb_app_id = process.env.FB_APP_ID;\n        winston.debug(\"Whatsapp fb_app_id: \"+ fb_app_id);\n\n        let configuration_id = process.env.CONFIGURATION_ID;\n        winston.debug(\"Whatsapp configuration_id: \"+ configuration_id);\n\n        let baseFileUrl = process.env.BASE_FILE_URL || apiUrl || \"http://localhost:3000\"\n\n        let job_topic = process.env.JOB_TOPIC_EXCHANGE;\n        winston.debug(\"Whatsapp job topic \" + job_topic);\n\n        let amqp_manager_url = process.env.AMQP_MANAGER_URL;\n        winston.debug(\"amqp_manager_url \" + amqp_manager_url);\n\n        let brand_name = null;\n        if (process.env.BRAND_NAME) {\n            brand_name = process.env.BRAND_NAME\n        }\n\n        let log = process.env.WHATSAPP_LOG || false\n        winston.debug(\"Whatsapp log: \"+ log);\n\n        whatsapp.startApp({\n            MONGODB_URL: config.databaseUri,          \n            dbconnection: dbConnection,\n            API_URL: apiUrl,\n            BASE_FILE_URL: baseFileUrl,\n            GRAPH_URL: graph_url,\n            FB_APP_ID: fb_app_id,\n            CONFIGURATION_ID: configuration_id,\n            BASE_URL: apiUrl + \"/modules/whatsapp\",                     \n            APPS_API_URL: apiUrl + \"/modules/apps\",\n            REDIS_HOST: host,\n            REDIS_PORT: port,\n            REDIS_PASSWORD: password,\n            AMQP_MANAGER_URL: amqp_manager_url,\n            JOB_TOPIC_EXCHANGE: job_topic,\n            BRAND_NAME: brand_name,\n            log: log\n        }, (err) => {\n            if (!err) {\n                winston.info(\"Tiledesk Messenger Connector proxy server succesfully started.\");\n            } else {\n                winston.info(\"unable to start Tiledesk Whatsapp Connector. \" + err);\n            }\n        })\n\n    }\n}\n\nvar listener = new Listener();\n\nmodule.exports = listener;"
  },
  {
    "path": "routes/admin.js",
    "content": "const fs = require('fs');\n\nvar express = require('express');\n\nvar router = express.Router();\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token');\nvar roleChecker = require('../middleware/has-role');\n\n\nrouter.get('/', function (req, res) {\n\n    \n    res.render('admin-get', { title: 'Environment config', \n        FIREBASE_PRIVATE_KEY:process.env.FIREBASE_PRIVATE_KEY,\n        FIREBASE_CLIENT_EMAIL:process.env.FIREBASE_CLIENT_EMAIL,\n        FIREBASE_PROJECT_ID:process.env.FIREBASE_PROJECT_ID,\n        FIREBASE_APIKEY:process.env.FIREBASE_APIKEY,        \n        FIREBASE_AUTHDOMAIN:process.env.FIREBASE_AUTHDOMAIN,\n        FIREBASE_DATABASEURL:process.env.FIREBASE_DATABASEURL,\n        FIREBASE_STORAGEBUCKET:process.env.FIREBASE_STORAGEBUCKET,\n        FIREBASE_MESSAGINGSENDERID:process.env.FIREBASE_MESSAGINGSENDERID,\n        CHAT21_ENABLED:process.env.CHAT21_ENABLED,\n        CHAT21_URL:process.env.CHAT21_URL,\n        CHAT21_APPID:process.env.CHAT21_APPID,\n        CHAT21_ADMIN_TOKEN:process.env.CHAT21_ADMIN_TOKEN,\n\n    })\n});\n\n\nrouter.get('/saved', function (req, res) {\n\n\n    res.render('admin-saved-get', { title: 'Environment config saved.',\n    })\n});\n\nrouter.post('/', function (req, res) {\n    \n    var env = `\n    FIREBASE_PRIVATE_KEY=${req.body.FIREBASE_PRIVATE_KEY}\n    FIREBASE_CLIENT_EMAIL=${req.body.FIREBASE_CLIENT_EMAIL}\n    FIREBASE_PROJECT_ID=${req.body.FIREBASE_PROJECT_ID}\n    FIREBASE_APIKEY=${req.body.FIREBASE_APIKEY}\n    FIREBASE_AUTHDOMAIN=${req.body.FIREBASE_AUTHDOMAIN}\n    FIREBASE_DATABASEURL=${req.body.FIREBASE_DATABASEURL}\n    FIREBASE_STORAGEBUCKET=${req.body.FIREBASE_STORAGEBUCKET}    \n    FIREBASE_MESSAGINGSENDERID=${req.body.FIREBASE_MESSAGINGSENDERID}\n    CHAT21_ENABLED=${req.body.CHAT21_ENABLED}\n    CHAT21_URL=${req.body.CHAT21_URL}\n    CHAT21_APPID=${req.body.CHAT21_APPID}\n    CHAT21_ADMIN_TOKEN=${req.body.CHAT21_ADMIN_TOKEN}\n    `;\n    fs.writeFile(__dirname+\"/../confenv/.env\",env, function(err) {\n\n        if(err) {\n            console.log(err);\n            return res.redirect(303, '/admin/') // Notice the 303 parameter\n        }\n    \n        console.log(\"The env file was saved!\");\n        // res.end()\n        res.redirect(303, '/admin/saved') // Notice the 303 parameter\n    }); \n\n    \n  });\n\n  module.exports = router;\n"
  },
  {
    "path": "routes/answered.js",
    "content": "const express = require('express');\nconst router = express.Router();\nconst { Namespace, AnsweredQuestion } = require('../models/kb_setting');\nconst winston = require('../config/winston');\n\n// Add a new unanswerd question\nrouter.post('/', async (req, res) => {\n    try {\n        const { namespace, question, answer, tokens, request_id } = req.body;\n        const id_project = req.projectid;\n\n        if (!namespace || !question || !answer) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameters: namespace, question and answer\"\n            })\n        }\n\n        // Check if namespae belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            })\n        }\n\n        const answeredQuestion = new AnsweredQuestion({\n            id_project,\n            namespace,\n            question,\n            answer,\n            tokens,\n            request_id,\n        });\n\n        const savedQuestion = await answeredQuestion.save();\n        res.status(200).json(savedQuestion);\n\n    } catch (error) {\n        winston.error('Error adding answered question:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error adding answered question\"\n        });\n    }\n})\n\n// Get all answered questions for a namespace\nrouter.get('/:namespace', async (req, res) => {\n\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        if (!namespace) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameter: namespace\"\n            })\n        }\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            })\n        }\n        \n        const page = parseInt(req.query.page) || 0;\n        const limit = parseInt(req.query.limit) || 20;\n        const sortField = req.query.sortField || 'created_at';\n        const direction = parseInt(req.query.direction) || -1;\n\n        const filter = { id_project, namespace };\n\n        let projection = undefined;\n\n        if (req.query.search) {\n            filter.$text = { $search: req.query.search };\n            // Add score to projection if it's a text search\n            projection = { score: { $meta: \"textScore\" } };\n        }\n\n        let sortObj;\n        if (projection && projection.score) {\n            sortObj = { score: { $meta: \"textScore\" } };\n        } else {\n            sortObj = { [sortField]: direction };\n        }\n\n        const questions = await AnsweredQuestion.find(filter, projection)\n            .sort(sortObj)\n            .skip(page * limit)\n            .limit(limit);\n\n        const count = await AnsweredQuestion.countDocuments(filter);\n\n        res.status(200).json({\n            count,\n            questions,\n            query: {\n                page,\n                limit,\n                sortField,\n                direction,\n                search: req.query.search || undefined\n            }\n        });\n        \n    } catch (error) {\n        winston.error('Error getting answered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error getting answered questions\"\n        });\n    }\n\n})\n\nrouter.delete('/:id', async (req, res) => {\n    try {\n        const { id } = req.params;\n        const id_project = req.projectid;\n\n        const deleted = await AnsweredQuestion.findOneAndDelete({ _id: id, id_project });\n        if (!deleted) {\n            return res.status(404).json({\n                success: false,\n                error: \"Question not found\"\n            });\n        }\n\n        res.status(200).json({\n            success: true,\n            message: \"Question deleted successfully\"\n        });\n\n    } catch (error) {\n        winston.error('Error deleting answered question:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error deleting answered question\"\n        });\n    }\n})\n\nrouter.delete('/namespace/:namespace', async (req, res) => {\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n        \n        const result = await AnsweredQuestion.deleteMany({ id_project, namespace });\n        res.status(200).json({\n            success: true,\n            count: result.deletedCount,\n            message: \"All questions deleted successfully\"\n        });\n\n    } catch (error) {\n        winston.error('Error deleting answered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error deleting answered questions\"\n        });\n    }\n})\n\nrouter.get('/count/:namespace', async (req, res) => {\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        if (!namespace) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameter: namespace\"\n            });\n        }\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n\n        const count = await AnsweredQuestion.countDocuments({ id_project, namespace });\n        res.status(200).json({ count });\n\n    } catch (error) {\n        winston.error('Error counting answered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error counting answered questions\"\n        });\n    }\n})\n\n// Helper function to validate namespace\nasync function validateNamespace(id_project, namespace_id) {\n    try {\n        const namespace = await Namespace.findOne({\n            id_project: id_project,\n            id: namespace_id\n        });\n        return !!namespace;\n    } catch (err) {\n        winston.error('validate namespace error: ', err);\n        throw err;\n    }\n}\n\nmodule.exports = router;"
  },
  {
    "path": "routes/auth.js",
    "content": "var config = require('../config/database');\nvar express = require('express');\nvar jwt = require('jsonwebtoken');\nvar router = express.Router();\nvar User = require(\"../models/user\");\nvar Subscription = require(\"../models/subscription\");\nvar Project_user = require(\"../models/project_user\");\nvar RoleConstants = require(\"../models/roleConstants\");\nvar uniqid = require('uniqid');\nvar emailService = require(\"../services/emailService\");\nvar pendinginvitation = require(\"../services/pendingInvitationService\");\nvar userService = require(\"../services/userService\");\n\nvar noentitycheck = require('../middleware/noentitycheck');\n\nvar winston = require('../config/winston');\nconst uuidv4 = require('uuid/v4');\n\nvar authEvent = require(\"../event/authEvent\");\n\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token');\nvar PendingInvitation = require(\"../models/pending-invitation\");\nconst { check, validationResult } = require('express-validator');\nvar UserUtil = require('../utils/userUtil');\n\nlet configSecret = process.env.GLOBAL_SECRET || config.secret;\nvar pKey = process.env.GLOBAL_SECRET_OR_PRIVATE_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n  configSecret = pKey.replace(/\\\\n/g, '\\n');\n}\n\nlet pubConfigSecret = process.env.GLOBAL_SECRET || config.secret;\nvar pubKey = process.env.GLOBAL_SECRET_OR_PUB_KEY;\nif (pubKey) {\n  pubConfigSecret = pubKey.replace(/\\\\n/g, '\\n');\n}\n\nvar recaptcha = require('../middleware/recaptcha');\nconst errorCodes = require('../errorCodes');\n\n\n\n// const fs  = require('fs');\n// var configSecret = fs.readFileSync('private.key');\n\n\nrouter.post('/signup',\n  [\n    check('email').isEmail(),\n    check('firstname').notEmpty(),\n    check('lastname').notEmpty(),\n    recaptcha\n\n  ]\n  // recaptcha.middleware.verify\n\n, function (req, res) {\n\n  // if (!req.recaptcha.error) {\n  //   winston.error(\"Signup recaptcha ok\");\n  // } else {\n  //   // error code\n  //   winston.error(\"Signup recaptcha ko\");\n  // }\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    winston.error(\"Signup validation error\", errors);\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n  if (!req.body.email || !req.body.password) {\n    winston.error(\"Signup validation error. Email or password is missing\", {email: req.body.email, password: req.body.password});\n    return res.json({ success: false, msg: 'Please pass email and password.' });\n  } else {\n\n    // TODO: move the regex control inside signup method of UserService.\n    // Warning: the pwd used in every test must be changed!\n    // const regex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,}$/);\n    // if (!regex.test(req.body.password)) {\n    //   return res.status(403).send({ success: false, message: \"The password does not meet the minimum vulnerability requirements\"})\n    // }\n\n    return userService.signup(req.body.email, req.body.password, req.body.firstname, req.body.lastname, false, req.body.phone)\n      .then( async function (savedUser) {\n\n        winston.debug('-- >> -- >> savedUser ', savedUser.toObject());\n\n        let skipVerificationEmail = false;\n        if (req.headers.authorization) {\n\n          let token = req.headers.authorization.split(\" \")[1];\n          let decode = jwt.verify(token, pubConfigSecret)\n          if (decode && (decode.email === process.env.ADMIN_EMAIL)) {\n            let updatedUser = await User.findByIdAndUpdate(savedUser._id, { emailverified: true }, { new: true }).exec();\n            winston.debug(\"updatedUser: \", updatedUser);\n            skipVerificationEmail = true;\n            winston.verbose(\"skip sending verification email\")\n          }\n        }\n\n        if (!req.body.disableEmail){\n          if (!skipVerificationEmail) {\n\n            let verify_email_code = uniqid();\n            winston.verbose(\"(Auth) verify_email_code: \", verify_email_code);\n\n            let redis_client = req.app.get('redis_client');\n            let key = \"emailverify:verify-\" + verify_email_code;\n            let obj = { _id: savedUser._id, email: savedUser.email}\n            let value = JSON.stringify(obj);\n            redis_client.set(key, value, { EX: 900} )\n            emailService.sendVerifyEmailAddress(savedUser.email, savedUser, verify_email_code);\n          }\n        }\n\n        // if (!req.body.disableEmail){\n        //     emailService.sendVerifyEmailAddress(savedUser.email, savedUser);\n        // }\n\n\n        /*\n         * *** CHECK THE EMAIL OF THE NEW USER IN THE PENDING INVITATIONS TABLE ***\n         * IF EXIST MEANS THAT THE NEW USER HAS BEEN INVITED TO A PROJECT WHEN IT HAS NOT YET REGISTERED\n         * SO IF ITS EMAIL EXIST IN THE PENDING INVITATIONS TABLE ARE CREATED THE PROJECT USER FOR THE PROJECTS\n         * TO WHICH WAS INVITED, AT THE SAME TIME THE USER ARE DELETED FROM THE PENDING INVITATION TABLE\n         */\n        pendinginvitation.checkNewUserInPendingInvitationAndSavePrcjUser(savedUser.email, savedUser._id);\n          // .then(function (projectUserSaved) {\n          //   return res.json({ msg: \"Saved project user \", projectUser: projectUserSaved });\n          // }).catch(function (err) {\n          //   return res.send(err);\n          // });\n\n\n          authEvent.emit(\"user.signup\", {savedUser: savedUser, req: req});\n\n\n          //remove password\n          let userJson = savedUser.toObject();\n          delete userJson.password;\n\n\n         res.json({ success: true, msg: 'Successfully created new user.', user: userJson });\n      }).catch(function (err) {\n\n        winston.error('Error registering new user', err);\n        authEvent.emit(\"user.signup.error\",  {req: req, err:err});\n\n        if (err.code === 11000) {\n          res.status(403).send({ success: false, message: \"Email already registered\" });\n        } else {\n          res.status(500).send({ success: false, message: \"Registration cannot be completed\" });\n        }\n\n      });\n  }\n});\n\n\n\n\n\n// curl -v -X POST -H 'Content-Type:application/json' -u 6b4d2080-3583-444d-9901-e3564a22a79b@tiledesk.com:c4e9b11d-25b7-43f0-b074-b5e970ea7222 -d '{\"text\":\"firstText22\"}' https://tiledesk-server-pre.herokuapp.com/5df2240cecd41b00173a06bb/requests/support-group-554477/messages\n\nrouter.post('/signinAnonymously',\n[\n  check('id_project').notEmpty(),\n],\nfunction (req, res) {\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    winston.error(\"SigninAnonymously validation error\", {errors: errors, reqBody: req.body, reqUrl: req.url });\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n  let uid = uuidv4();\n  let shortuid = uid.substring(0,4);\n  var firstname = req.body.firstname || \"guest#\"+shortuid; // guest_here\n  // var firstname = req.body.firstname || \"Guest\"; // guest_here\n\n\n\n  //TODO togli trattini da uuidv4()\n\n// TODO remove email.sec?\n  let userAnonym = {_id: uid, firstname:firstname, lastname: req.body.lastname, email: req.body.email, attributes: req.body.attributes};\n\n  req.user = UserUtil.decorateUser(userAnonym);\n\n    var newProject_user = new Project_user({\n      id_project: req.body.id_project, //attentoqui\n      uuid_user: req.user._id,\n      role: RoleConstants.GUEST,\n      roleType : RoleConstants.TYPE_USERS,\n      user_available: true,\n      createdBy: req.user._id,\n      updatedBy: req.user._id\n    });\n\n        return newProject_user.save(function (err, savedProject_user) {\n          if (err) {\n            winston.error('Error saving object.', err)\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }\n\n\n          var signOptions = {\n            issuer:  'https://tiledesk.com',\n            subject:  'guest',\n            audience:  'https://tiledesk.com',\n            jwtid: uuidv4()\n          };\n\n          var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n          if (alg) {\n            signOptions.algorithm = alg;\n          }\n\n          var token = jwt.sign(userAnonym, configSecret, signOptions); //priv_jwt pp_jwt\n\n\n          authEvent.emit(\"user.signin\", {user:userAnonym, req:req, jti:signOptions.jwtid, token: 'JWT ' + token});\n\n          authEvent.emit(\"projectuser.create\", savedProject_user);\n\n          winston.debug('project user created ', savedProject_user.toObject());\n\n          res.json({ success: true, token: 'JWT ' + token, user: userAnonym });\n      });\n\n\n});\n\n\n\n\nrouter.post('/signinWithCustomToken', [\n  // function(req,res,next) {req.disablePassportEntityCheck = true;winston.debug(\"disablePassportEntityCheck=true\"); next();},\n  noentitycheck,\n  passport.authenticate(['jwt'], { session: false }),\n  validtoken], async (req, res) => {\n\n    winston.debug(\"signinWithCustomToken req: \", req );\n\n    if (!req.user.aud) { //serve??\n      winston.warn(\"SigninWithCustomToken JWT Aud field is required\", req.user );\n      return res.status(400).send({ success: false, msg: 'JWT Aud field is required' });\n    }\n    // TODO add required jti?\n    // if (!req.user.jti) { \n    //   return res.status(400).send({ success: false, msg: 'JWT JTI field is required' });\n    // }\n\n    const audUrl  = new URL(req.user.aud);\n    winston.debug(\"audUrl: \"+ audUrl );\n    const path = audUrl.pathname;\n    winston.debug(\"audUrl path: \" + path );\n\n    const AudienceType = path.split(\"/\")[1];\n    winston.debug(\"audUrl AudienceType: \" + AudienceType );\n\n    var id_project;\n\n    let userToReturn = req.user;\n\n    var role = RoleConstants.USER;\n\n    //problema wp da testare\n    if (AudienceType === \"subscriptions\") {\n\n      const AudienceId = path.split(\"/\")[2];\n      winston.debug(\"audUrl AudienceId: \" + AudienceId );\n\n      if (!AudienceId) {\n        winston.warn(\"JWT Aud.AudienceId field is required for AudienceType subscriptions\", req.user );\n        return res.status(400).send({ success: false, msg: 'JWT Aud.AudienceId field is required for AudienceType subscriptions' });\n      }\n\n      var subscription = await Subscription.findById(AudienceId).exec();\n      winston.debug(\"signinWithCustomToken subscription: \", subscription );\n      id_project = subscription.id_project;\n      winston.debug(\"signinWithCustomToken subscription req.user._id: \"+ req.user._id );\n      winston.debug(\"signinWithCustomToken subscription.id_project:\"+ id_project );\n\n    } else if (AudienceType===\"projects\") {\n\n      const AudienceId = path.split(\"/\")[2];\n      winston.debug(\"audUrl AudienceId: \" + AudienceId );\n\n      if (!AudienceId) {\n        winston.warn(\"JWT Aud.AudienceId field is required for AudienceType projects\", req.user );\n        return res.status(400).send({ success: false, msg: 'JWT Aud.AudienceId field is required for AudienceType projects' });\n      }\n\n      id_project = AudienceId;\n\n\n    } else {\n      winston.debug(\"audience generic\");\n      if (req.body.id_project) {\n        id_project = req.body.id_project;\n        winston.verbose(\"audience generic. id_project is passed explicitly\");\n      }else {\n        // When happen? when an agent (or admin) from ionic find a tiledesk token in the localstorage (from dashboard) and use signinWithCustomToken to obtain user object\n        return res.json({ success: true, token: req.headers[\"authorization\"], user: req.user });\n      }\n\n    }\n\n\n\n    if (req.user.role) {\n      role = req.user.role;\n    }\n    winston.debug(\"role1: \" + role );\n    winston.debug(\"id_project: \" + id_project + \" uuid_user \" + req.user._id + \" role \" + role);\n\n\n      Project_user.findOne({ id_project: id_project, uuid_user: req.user._id}).\n      // Project_user.findOne({ id_project: id_project, uuid_user: req.user._id,  role: role}).\n      exec(async (err, project_user) => {\n        if (err) {\n          winston.error(err);\n          return res.json({ success: true, token: req.headers[\"authorization\"], user: req.user });\n        }\n        winston.debug(\"project_user: \", project_user );\n\n\n        if (!project_user) {\n\n          let createNewUser = false;\n          winston.debug('role2: '+ role)\n\n\n          if (role === RoleConstants.OWNER || role === RoleConstants.ADMIN || role === RoleConstants.AGENT) {\n           createNewUser = true;\n           winston.debug('role owner or admin or agent');\n           var newUser;\n           try {\n\n            // Bug with email in camelcase\n            newUser = await userService.signup(req.user.email.toLowerCase(), uuidv4(), req.user.firstname, req.user.lastname, false);\n           } catch(e) {\n            winston.debug('error signup already exists??: ')\n\n            if (e.code = \"E11000\") {\n              newUser = await User.findOne({email: req.user.email.toLowerCase(), status: 100}).exec();\n              winston.debug('signup found')\n                  // qui dovresti cercare pu sul progetto con id di newUser se c'è\n              var  project_userUser = await Project_user.findOne({ id_project: id_project, id_user: newUser._id}).exec();\n                  if (project_userUser) {\n                    winston.debug('project user found')\n                    if (project_userUser.status===\"active\") {\n                        var signOptions = {\n                          issuer:  'https://tiledesk.com',\n                          subject:  'user',\n                          audience:  'https://tiledesk.com',\n                          jwtid: uuidv4()\n                        };\n\n                        var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n                        if (alg) {\n                          signOptions.algorithm = alg;\n                        }\n                        winston.debug('project user found2')\n\n                        //remove password //test it\n                        let userJson = newUser.toObject();\n                        delete userJson.password;\n                        winston.debug('project user found3')\n\n                        let returnToken = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n                        winston.debug('project user found4')\n\n                        if (returnToken.indexOf(\"JWT\")<0) {\n                          returnToken = \"JWT \" + returnToken;\n                        }\n                        winston.debug('project user found5')\n\n                        return res.json({ success: true, token: returnToken, user: newUser });\n\n                    }\n                  }\n\n            }\n           }\n\n           if (!newUser) {\n            return res.status(401).send({ success: false, msg: 'User not found.' });\n           }\n\n           winston.debug('userToReturn forced to newUser.', newUser)\n           userToReturn=newUser;\n\n\n\n          }\n\n            var newProject_user = new Project_user({\n\n              id_project: id_project,\n              uuid_user: req.user._id,\n              // id_user: req.user._id,\n              role: role,\n              roleType : RoleConstants.TYPE_USERS, //RICONtROLLA QUIA \n              user_available: true,\n              createdBy: req.user._id, //oppure req.user.id attento problema\n              updatedBy: req.user._id\n            });\n\n            winston.debug('newProject_user', newProject_user);\n\n            // testtare qiestp cpm dpcker dev partemdp da ui\n            if (createNewUser===true) {\n              newProject_user.id_user = newUser._id;\n              // delete newProject_user.uuid_user;\n              winston.debug('newProject_user.', newProject_user)\n            }\n\n            return newProject_user.save(function (err, savedProject_user) {\n              if (err) {\n                winston.error('Error saving object.', err)\n                // return res.status(500).send({ success: false, msg: 'Error saving object.' });\n                return res.json({ success: true, token: req.headers[\"authorization\"], user: userToReturn});\n              }\n\n\n              authEvent.emit(\"projectuser.create\", savedProject_user);\n\n              authEvent.emit(\"user.signin\", {user:userToReturn, req:req, token: req.headers[\"authorization\"]});\n\n              winston.debug('project user created ', savedProject_user.toObject());\n\n\n              let returnToken = req.headers[\"authorization\"];\n              if (createNewUser===true) {\n\n\n\n                var signOptions = {\n                  issuer:  'https://tiledesk.com',\n                  subject:  'user',\n                  audience:  'https://tiledesk.com',\n                  jwtid: uuidv4()\n                };\n\n                var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n                if (alg) {\n                  signOptions.algorithm = alg;\n                }\n\n                //remove password //test it\n                let userJson = userToReturn.toObject();\n                delete userJson.password;\n\n                returnToken = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n              }\n\n              winston.debug('returnToken '+returnToken);\n\n              winston.debug('returnToken.indexOf(\"JWT\") '+returnToken.indexOf(\"JWT\"));\n\n              if (returnToken.indexOf(\"JWT\")<0) {\n                returnToken = \"JWT \" + returnToken;\n              }\n\n              return res.json({ success: true, token: returnToken, user: userToReturn });\n          });\n        } else {\n          winston.debug('project user already exists ');\n\n          if (project_user.status===\"active\") {\n\n            winston.debug('role.'+role)\n            winston.debug(' project_user.role', project_user)\n\n\n             if (role == project_user.role) {\n               winston.debug('equals role : '+role + \" \" + project_user.role);\n             } else {\n               winston.debug('different role : '+role + \" \" + project_user.role);\n             }\n            // rolecheck\n            if (req.user.role && (req.user.role === RoleConstants.OWNER || req.user.role === RoleConstants.ADMIN || req.user.role === RoleConstants.AGENT)) {\n              let userFromDB = await User.findOne({email: req.user.email.toLowerCase(), status: 100}).exec();\n\n              var signOptions = {\n                issuer:  'https://tiledesk.com',\n                subject:  'user',\n                audience:  'https://tiledesk.com',\n                jwtid: uuidv4()\n              };\n\n              var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n              if (alg) {\n                signOptions.algorithm = alg;\n              }\n\n              //remove password //test it\n              let userJson = userFromDB.toObject();\n              delete userJson.password;\n\n              let returnToken = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n\n              if (returnToken.indexOf(\"JWT\")<0) {\n                returnToken = \"JWT \" + returnToken;\n              }\n              return res.json({ success: true, token: returnToken, user: userFromDB });\n              // return res.json({ success: true, token: req.headers[\"authorization\"], user: userFromDB });\n\n\n            } else {\n              winston.debug('req.headers[\"authorization\"]: '+req.headers[\"authorization\"]);\n\n              return res.json({ success: true, token: req.headers[\"authorization\"], user: userToReturn });\n            }\n\n\n          } else {\n            winston.warn('Authentication failed. Project_user not active.');\n            return res.status(401).send({ success: false, msg: 'Authentication failed. Project_user not active.' });\n          }\n\n        }\n\n\n      });\n\n});\n\n\n\n\n\n\n// TODO aggiungere logout? con user.logout event?\n// router.post('/logout',\n//   [passport.authenticate(['jwt'], {session: false}), validtoken],\n//   function (req, res) {\n//     authEvent.emit(\"user.logout\", {user: req.user, req: req});\n//     req.logout();\n//     res.json({ success: true, msg: 'Logout successful.' });\n// });\n\nrouter.post('/signin',\n[\n  // check('email').notEmpty(),\n  check('email').isEmail(),\n  check('password').notEmpty(),\n],\nfunction (req, res) {\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    winston.error(\"Signin validation error\", errors);\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n  var email = req.body.email.toLowerCase();\n\n  winston.debug(\"email\", email);\n  User.findOne({\n    email: email, status: 100\n  }, 'email firstname lastname password emailverified id', function (err, user) {\n    if (err) {\n      winston.error(\"Error signin\", err);\n      throw err;\n    }\n\n    if (!user) {\n      authEvent.emit(\"user.signin.error\", {req: req});\n\n      winston.warn('Authentication failed. User not found.', {email:email});\n      res.status(401).send({ success: false, msg: 'Authentication failed. User not found.' });\n    } else {\n      // check if password matches\n\n      if (req.body.password) {\n        var superPassword = process.env.SUPER_PASSWORD || \"superadmin\";\n\n        // TODO externalize iss aud sub\n\n        // https://auth0.com/docs/api-auth/tutorials/verify-access-token#validate-the-claims\n        var signOptions = {\n          //         The \"iss\" (issuer) claim identifies the principal that issued the\n          //  JWT.  The processing of this claim is generally application specific.\n          //  The \"iss\" value is a case-sensitive string containing a StringOrURI\n          //  value.  Use of this claim is OPTIONAL.\n          issuer:  'https://tiledesk.com',\n\n  //         The \"sub\" (subject) claim identifies the principal that is the\n  //  subject of the JWT.  The claims in a JWT are normally statements\n  //  about the subject.  The subject value MUST either be scoped to be\n  //  locally unique in the context of the issuer or be globally unique.\n  //  The processing of this claim is generally application specific.  The\n  //  \"sub\" value is a case-sensitive string containing a StringOrURI\n  //  value.  Use of this claim is OPTIONAL.\n\n          // subject:  user._id.toString(),\n          // subject:  user._id+'@tiledesk.com/user',\n          subject:  'user',\n\n  //         The \"aud\" (audience) claim identifies the recipients that the JWT is\n  //  intended for.  Each principal intended to process the JWT MUST\n  //  identify itself with a value in the audience claim.  If the principal\n  //  processing the claim does not identify itself with a value in the\n  //  \"aud\" claim when this claim is present, then the JWT MUST be\n  //  rejected.  In the general case, the \"aud\" value is an array of case-\n  //  sensitive strings, each containing a StringOrURI value.  In the\n  //  special case when the JWT has one audience, the \"aud\" value MAY be a\n  //  single case-sensitive string containing a StringOrURI value.  The\n  //  interpretation of audience values is generally application specific.\n  //  Use of this claim is OPTIONAL.\n\n          audience:  'https://tiledesk.com',\n\n          // uid: user._id  Uncaught ValidationError: \"uid\" is not allowed\n          // expiresIn:  \"12h\",\n          // algorithm:  \"RS256\"\n\n\n          jwtid: uuidv4()\n\n        };\n\n        var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n        if (alg) {\n          signOptions.algorithm = alg;\n        }\n\n         //remove password //test it\n         let userJson = user.toObject();\n         delete userJson.password;\n\n        if (superPassword && superPassword == req.body.password) {\n          var token = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n          // return the information including token as JSON\n          res.json({ success: true, token: 'JWT ' + token, user: user });\n        } else {\n          user.comparePassword(req.body.password, function (err, isMatch) {\n            if (isMatch && !err) {\n              // if user is found and password is right create a token\n              var token = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n              authEvent.emit(\"user.signin\", {user:user, req:req, jti:signOptions.jwtid, token: 'JWT ' + token});\n\n              var returnObject = { success: true, token: 'JWT ' + token, user: userJson };\n\n              var adminEmail = process.env.ADMIN_EMAIL || \"admin@tiledesk.com\";\n              if (email === adminEmail) {\n                returnObject.role = \"admin\";\n              }\n\n              // return the information including token as JSON\n              res.json(returnObject);\n            } else {\n              winston.warn('Authentication failed. Wrong password for email: ' + email);\n              res.status(401).send({ success: false, msg: 'Authentication failed. Wrong password.' });\n            }\n          });\n\n        }\n      } else {\n        winston.warn('Authentication failed.  Password is required.', {body: req.body});\n        res.status(401).send({ success: false, msg: 'Authentication failed.  Password is required.' });\n      }\n\n\n    }\n  });\n});\n\n\n// http://localhost:3000/auth/google?redirect_url=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fgoogle%2Fcallback%3Ffrom%3Dsignup\n\n// http://localhost:3000/auth/google?redirect_url=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fgoogle%2Fcallbacks\n\n// http://localhost:3000/auth/google?redirect_url=%2F%23%2Fproject%2F6452281f6d68c5f419c1c577%2Fhome\n\n// http://localhost:3000/auth/google?redirect_url=%23%2Fcreate-project-gs\n\n// http://localhost:3000/auth/google?forced_redirect_url=https%3A%2F%2Fpanel.tiledesk.com%2Fv3%2Fchat%2F%23conversation-detail%3Ffrom%3Dgoogle\n\n// https://tiledesk-server-pre.herokuapp.com/auth/google?redirect_url=%23%2Fcreate-project-gs\n\n// https://tiledesk-server-pre.herokuapp.com/auth/google\n\n// https://tiledesk-server-pre.herokuapp.com/auth/google?forced_redirect_url=https%3A%2F%2Fpanel.tiledesk.com%2Fv3%2Fchat%2F%23conversation-detail%3Ffrom%3Dgoogle\n\n// Redirect the user to the Google signin page</em> \n// router.get(\"/google\", passport.authenticate(\"google\", { scope: [\"email\", \"profile\"] }));\nrouter.get(\"/google\", function(req,res,next){\n  winston.debug(\"redirect_url: \"+ req.query.redirect_url );\n  req.session.redirect_url = req.query.redirect_url;\n\n  winston.debug(\"forced_redirect_url: \"+ req.query.forced_redirect_url );\n  req.session.forced_redirect_url = req.query.forced_redirect_url;\n\n  // req._toParam = 'Hello';\n  passport.authenticate(\n      // 'google', { scope : [\"email\", \"profile\"], state: base64url(JSON.stringify({blah: 'text'}))  } //custom redirect_url req.query.state\n      'google', { scope : [\"email\", \"profile\"], prompt: 'select_account' } //custom redirect_url\n      // 'google', { scope : [\"email\", \"profile\"], callbackURL: req.query.redirect_url } //custom redirect_url\n  )(req,res,next);\n});\n\n// router.get(\"/google/callbacks\", passport.authenticate(\"google\", { session: false }), (req, res) => {\n//   console.log(\"callback_signup\");\n//   res.redirect(\"/google/callback\");\n// });\n\n// Retrieve user data using the access token received</em> \nrouter.get(\"/google/callback\", passport.authenticate(\"google\", { session: false }), (req, res) => {\n// res.redirect(\"/auth/profile/\");\n\n  var user = req.user;\n  winston.debug(\"user\", user);\n  // winston.info(\"req._toParam: \"+ req._toParam);\n  // winston.info(\"req.query.redirect_url: \"+ req.query.redirect_url);\n  // winston.info(\"req.query.state: \"+ req.query.state);\n  winston.debug(\"req.session.redirect_url: \"+ req.session.redirect_url);\n\n\n  var userJson = user.toObject();\n\n  delete userJson.password;\n\n\n    var signOptions = {\n      issuer:  'https://tiledesk.com',\n      subject:  'user',\n      audience:  'https://tiledesk.com',\n      jwtid: uuidv4()\n\n    };\n\n    var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n    if (alg) {\n      signOptions.algorithm = alg;\n    }\n\n\n  var token = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n\n  // return the information including token as JSON\n  // res.json(returnObject);\n\n  let dashboard_base_url = process.env.EMAIL_BASEURL || config.baseUrl;\n  winston.debug(\"Google Redirect dashboard_base_url: \", dashboard_base_url);\n\n  let homeurl = \"/#/\";\n\n  if (req.session.redirect_url) {\n    homeurl = req.session.redirect_url;\n  }\n\n  var url = dashboard_base_url+homeurl+\"?token=JWT \"+token;\n\n  if (req.session.forced_redirect_url) {\n    url = req.session.forced_redirect_url+\"?jwt=JWT \"+token;  //attention we use jwt= (ionic) instead token=(dashboard) for ionic\n  }\n\n  winston.debug(\"Google Redirect: \"+ url);\n\n  res.redirect(url);\n\n\n\n\n}\n);\n\n\n\nrouter.get(\"/oauth2\", function (req, res, next) {\n  winston.debug(\"(oauth2) redirect_url: \" + req.query.redirect_url);\n  req.session.redirect_url = req.query.redirect_url;\n\n  winston.debug(\"(oauth2) forced_redirect_url: \" + req.query.forced_redirect_url);\n  req.session.forced_redirect_url = req.query.forced_redirect_url;\n\n  passport.authenticate(\n    'oauth2', { prompt: 'select_account' }\n  )(req, res, next);\n});\n\n// router.get('/oauth2',\n//   passport.authenticate('oauth2'));\n\nrouter.get('/oauth2/callback', passport.authenticate('oauth2', { session: false }), function (req, res) {\n  winston.debug(\"'/oauth2/callback: \", req.query);\n  winston.debug(\"/oauth2/callback --> req.session.redirect_url\", req.session.redirect_url);\n  winston.debug(\"/oauth2/callback --> req.session.forced_redirect_url\", req.session.forced_redirect_url);\n\n  var user = req.user;\n  winston.debug(\"(/oauth2/callback) user\", user);\n  winston.debug(\"(/oauth2/callback) req.session.redirect_url: \" + req.session.redirect_url);\n  var userJson = user.toObject();\n\n  delete userJson.password;\n\n  var signOptions = {\n    issuer: 'https://tiledesk.com',\n    subject: 'user',\n    audience: 'https://tiledesk.com',\n    jwtid: uuidv4()\n\n  };\n\n  var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n  if (alg) {\n    signOptions.algorithm = alg;\n  }\n\n  var token = jwt.sign(userJson, configSecret, signOptions); //priv_jwt pp_jwt\n\n  // return the information including token as JSON\n  // res.json(returnObject);\n\n  let dashboard_base_url = process.env.EMAIL_BASEURL || config.baseUrl;\n  winston.debug(\"(/oauth2/callback) Google Redirect dashboard_base_url: \", dashboard_base_url);\n\n  let homeurl = \"/#/\";\n\n  const separator = homeurl.includes('?') ? '&' : '?';\n  var url = dashboard_base_url+homeurl+ separator + \"token=JWT \"+token;\n\n  if (req.session.redirect_url) {\n    const separator = req.session.redirect_url.includes('?') ? '&' : '?';\n    url = req.session.redirect_url+ separator + \"token=JWT \"+token;\n  }\n\n  if (req.session.forced_redirect_url) {\n    const separator = req.session.forced_redirect_url.includes('?') ? '&' : '?';\n    url = req.session.forced_redirect_url+ separator + \"jwt=JWT \"+token;  //attention we use jwt= (ionic) instead token=(dashboard) for ionic\n  }\n\n  winston.debug(\"(/oauth2/callback) Google Redirect: \" + url);\n\n  res.redirect(url);\n});\n\nrouter.get(\n  \"/keycloak\",\n  passport.authenticate(\"keycloak\")\n);\nrouter.get(\n  \"/keycloak/callback\",\n  passport.authenticate(\"keycloak\"),\n  function(req, res) {\n    winston.verbose(\"'/keycloak/callback: \");\n    // Successful authentication, redirect home.\n    res.redirect('/');\n  }\n);\n\n\n// profile route after successful sign in</em> \n// router.get(\"/profile\", (req, res) => {\n//   console.log(req);\n// res.send(\"Welcome\");\n// });\n\n// VERIFY EMAIL\nrouter.put('/verifyemail/:userid/:code', async function (req, res) {\n\n\n  winston.debug('VERIFY EMAIL - REQ BODY ', req.body);\n\n  let user_id = req.params.userid;\n  let verify_email_code = req.params.code;\n\n  if (!verify_email_code) {\n    return res.status(401).send({ success: false, error: \"Unable to verify email: missing verification code.\", error_code: errorCodes.AUTH.ERRORS.MISSING_VERIFICATION_CODE})\n  }\n\n  let redis_client = req.app.get('redis_client');\n  let key = \"emailverify:verify-\" + verify_email_code;\n  let value = await redis_client.get(key);\n  console.log(\"(Auth) verify value: \", value);\n  if (!value) {\n    return res.status(401).send({ success: false, error: \"Unable to verify email: the verification code is expired or invalid.\", error_code: errorCodes.AUTH.ERRORS.VERIFICATION_CODE_EXPIRED})\n  }\n\n  let basic_user = JSON.parse(value);\n  if (user_id !== basic_user._id) {\n    return res.status(401).send({ success: false, error: \"Trying to use a verification code from another user.\", error_code: errorCodes.AUTH.ERRORS.VERIFICATION_CODE_OTHER_USER})\n  }\n\n  User.findByIdAndUpdate(user_id, req.body, { new: true, upsert: true }, function (err, findUser) {\n    if (err) {\n      winston.error(err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n    winston.debug(findUser);\n    if (!findUser) {\n      winston.warn('User not found for verifyemail' );\n      return res.status(404).send({ success: false, msg: 'User not found', error_code: errorCodes.AUTH.ERRORS.USER_NOT_FOUND});\n    }\n    winston.debug('VERIFY EMAIL - RETURNED USER ', findUser);\n\n\n\n    res.json(findUser);\n  });\n});\n\n\n/**\n *! *** PENDING INVITATION NO AUTH ***\n */\nrouter.get('/pendinginvitationsnoauth/:pendinginvitationid', function (req, res) {\n\n  winston.debug('PENDING INVITATION NO AUTH GET BY ID - BODY ');\n\n  PendingInvitation.findById(req.params.pendinginvitationid, function (err, pendinginvitation) {\n    if (err) {\n      winston.error('PENDING INVITATION - ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!pendinginvitation) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(pendinginvitation);\n  });\n});\n\n/**\n * *** REQUEST RESET PSW ***\n * SEND THE RESET PSW EMAIL AND UPDATE THE USER OBJECT WITH THE PROPERTY new_psw_request\n * TO WHICH ASSIGN (AS VALUE) A UNIQUE ID\n */\nrouter.put('/requestresetpsw', function (req, res) {\n\n  winston.debug('REQUEST RESET PSW - EMAIL REQ BODY ', req.body);\n\n  var email = req.body.email.toLowerCase();\n  winston.debug(\"email\", email);\n\n// auttype\n  User.findOne({ email: email, status: 100\n    // , authType: 'email_password'\n  }, function (err, user) {\n    if (err) {\n      winston.error('REQUEST RESET PSW - ERROR ', err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n\n    if (!user) {\n      winston.warn('User not found.');\n      res.json({ success: false, msg: 'User not found.' });\n    } else if (user) {\n\n      winston.debug('REQUEST RESET PSW - USER FOUND ', user);\n      winston.debug('REQUEST RESET PSW - USER FOUND - ID ', user._id);\n      var reset_psw_request_id = uniqid()\n\n      winston.debug('REQUEST RESET PSW - UNIC-ID GENERATED ', reset_psw_request_id)\n\n      User.findByIdAndUpdate(user._id, { resetpswrequestid: reset_psw_request_id }, { new: true, upsert: true }).select(\"+resetpswrequestid\").exec(function (err, updatedUser) {\n\n        if (err) {\n          winston.error(err);\n          return res.status(500).send({ success: false, msg: err });\n        }\n\n        if (!updatedUser) {\n          winston.warn('User not found.');\n          return res.status(404).send({ success: false, msg: 'User not found' });\n        }\n\n        winston.debug('REQUEST RESET PSW - UPDATED USER ', updatedUser);\n\n        if (updatedUser) {\n\n          /**\n           * SEND THE PASSWORD RESET REQUEST EMAIL\n           */\n          emailService.sendPasswordResetRequestEmail(updatedUser.email, updatedUser.resetpswrequestid, updatedUser.firstname, updatedUser.lastname);\n\n\n        //  TODO emit user.update?\n          authEvent.emit('user.requestresetpassword', {updatedUser:updatedUser, req:req});\n\n          let userWithoutResetPassword = updatedUser.toJSON();\n          delete userWithoutResetPassword.resetpswrequestid;\n          delete userWithoutResetPassword._id;\n          delete userWithoutResetPassword.createdAt;\n          delete userWithoutResetPassword.updatedAt;\n          delete userWithoutResetPassword.__v;\n\n          // return res.json({ success: true, user: userWithoutResetPassword });\n          return res.json({ success: true, message: \"An email has been sent to reset your password\" });\n          // }\n          // catch (err) {\n          //   winston.debug('PSW RESET REQUEST - SEND EMAIL ERR ', err)\n          // }\n\n        }\n      });\n      // res.json({ success: true, msg: 'User found.' });\n    }\n  });\n\n});\n\n/**\n * *** RESET PSW ***\n */\nrouter.put('/resetpsw/:resetpswrequestid', function (req, res) {\n  winston.debug(\"--> RESET PSW - REQUEST ID\", req.params.resetpswrequestid);\n  winston.debug(\"--> RESET PSW - NEW PSW \", req.body.password);\n\n  User.findOne({ resetpswrequestid: req.params.resetpswrequestid }, function (err, user) {\n\n    if (err) {\n      winston.error('--> RESET PSW - Error getting user ', err)\n      return (err);\n    }\n\n    if (!user) {\n      winston.warn('--> RESET PSW - INVALID PSW RESET KEY');\n      return res.status(404).send({ success: false, msg: 'Invalid password reset key' });\n    }\n\n    if (user && req.body.password) {\n      winston.debug('--> RESET PSW - User Found ', user);\n      winston.debug('--> RESET PSW - User ID Found ', user._id);\n\n      user.password = req.body.password;\n      user.resetpswrequestid = '';\n\n      user.save(function (err, saveUser) {\n\n        if (err) {\n          winston.error('--- > USER SAVE -ERROR ', err)\n          return res.status(500).send({ success: false, msg: 'Error saving object.' });\n        }\n        winston.debug('--- > USER SAVED  ', saveUser)\n\n        emailService.sendYourPswHasBeenChangedEmail(saveUser.email, saveUser.firstname, saveUser.lastname);\n\n            //  TODO emit user.update?\n        authEvent.emit('user.resetpassword', {saveUser:saveUser, req:req});\n\n\n        res.status(200).json({ message: 'Password change successful', user: saveUser });\n\n      });\n    }\n  });\n})\n\n/**\n * CHECK IF EXSIST resetpswrequestid\n * if no\n */\nrouter.get('/checkpswresetkey/:resetpswrequestid', function (req, res) {\n  winston.debug(\"--> CHECK RESET PSW REQUEST ID\", req.params.resetpswrequestid);\n\n  User.findOne({ resetpswrequestid: req.params.resetpswrequestid }, function (err, user) {\n\n    if (err) {\n      winston.error('--> CHECK RESET PSW REQUEST ID - Error getting user ', err)\n      return (err);\n    }\n\n    if (!user) {\n      winston.warn('Invalid password reset key' );\n      return res.status(404).send({ success: false, msg: 'Invalid password reset key' });\n    }\n\n    if (user) {\n\n      res.status(200).json({ message: 'Valid password reset key', user: user });\n\n    }\n  });\n})\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/authtest.js",
    "content": "var express = require('express');\n\nvar router = express.Router();\n\nvar noentitycheck = require('../middleware/noentitycheck');\n\n\nrouter.get('/', \n function (req, res) {\n    res.send('{\"success\":true}');\n});\n  \n  \n  router.get('/noentitycheck', \n    [noentitycheck,\n    ], function (req, res) {\n    res.send('{\"success\":true}');\n  });\n  \n\nmodule.exports = router;"
  },
  {
    "path": "routes/authtestWithRoleCheck.js",
    "content": "var express = require('express');\n\nvar router = express.Router({mergeParams: true});\nvar roleChecker = require('../middleware/has-role');\n\nvar noentitycheck = require('../middleware/noentitycheck');\n\n\nrouter.get('/',  function (req, res) {\n    res.send('{\"success\":true}');\n  });\n  \n  router.get('/bot', [   \n    roleChecker.hasRoleOrTypes(null,['bot'])],\n     function (req, res) {\n    res.send('{\"success\":true}');\n  });\n  \n  router.get('/noentitycheck', \n    [noentitycheck,\n    ], function (req, res) {\n    res.send('{\"success\":true}');\n  });\n  \n\nmodule.exports = router;"
  },
  {
    "path": "routes/campaigns.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Group = require(\"../models/group\");\nvar Segment = require(\"../models/segment\");\nvar Lead = require(\"../models/lead\");\nvar User = require(\"../models/user\");\nvar winston = require('../config/winston');\nvar requestService = require(\"../services/requestService\");\nvar messageService = require(\"../services/messageService\");\nvar MessageConstants = require(\"../models/messageConstants\");\nvar UIDGenerator = require(\"../utils/UIDGenerator\");\nvar LeadConstants = require(\"../models/leadConstants\");\nvar Segment2MongoConverter = require(\"../utils/segment2mongoConverter\");\n\nvar JobManager = require(\"jobs-worker-queued\");\n\nvar JOB_RABBITURI = process.env.JOB_RABBITURI;\nwinston.verbose(\"JobWorkerQueued uri: \" + JOB_RABBITURI);\n\nvar jobManager = new JobManager(JOB_RABBITURI,\n  {\n  // debug:true,\n  // topic: \"test22\",\n  // exchange: \"test333\"\n  });\n\n\n// this endpoint supports support-group- or groups. this create a conversation for the sender (agent console)\nrouter.post('/', function (req, res) {\n\n  let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n  var request_id = req.body.request_id || 'support-group-' + req.projectid + \"-\" + UIDGenerator.generate();\n\n  // TODO cicla su segment\n\n\n  // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel) {\n\n  //TODO USE NEW requestService.create()\n  return requestService.createWithIdAndRequester(request_id, req.projectuser._id, req.body.leadid, req.projectid,\n    req.body.text, req.body.departmentid, req.body.sourcePage,\n    req.body.language, req.body.userAgent, null, req.user._id, req.body.attributes, req.body.subject, true, req.body.channel).then(function (savedRequest) {\n\n      winston.debug(\"savedRequest\", savedRequest);\n\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type) {\n      return messageService.create(req.body.sender || req.user._id, req.body.senderFullname || req.user.fullName, savedRequest.request_id, req.body.text,\n        req.projectid, req.user._id, messageStatus, req.body.attributes, req.body.type, req.body.metadata, req.body.language, MessageConstants.CHANNEL_TYPE.DIRECT, req.body.channel).then(function (savedMessage) {\n          res.json(savedMessage);\n        });\n\n    });\n});\n\n\n\n/*\nInvio di una campagna (il bot invia a tutti i membri del gruppo News dei messaggi direct)\nE' l'equivalente del bot \"Telegram\" che viene usato per aggiornare gli utenti delle ultime funzionalità. E' indicato per inviare news unidirezionali\n\ncurl -v -X POST -H 'Content-Type:application/json' -u XYZ:XYZ -d '{\"text\":\"Tiledesk new feature. See here https://tiledesk.com\", \"group_id\":\"XYZ\"}' https://api.tiledesk.com/v2/XYZ/campaigns/direct\n\nSpecifica nel campo text il messaggio.\n*/\n\nrouter.post('/directDEPRECATED?', async function (req, res) {\n\n  let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var recipients = [];\n\n  var recipient = req.body.recipient;\n  if (recipient) {\n    recipients.push(recipient);\n  }\n\n  // TODO cicla su segment\n  var segment_id = req.body.segment_id;\n  if (segment_id) {\n      winston.info(\"segment_id: \"+ segment_id);\n      \n      var queryLead = {};\n\n      let segment = await Segment.findOne({id_project: req.projectid, _id: segment_id }).exec();\n      if (!segment) {\n        return res.status(404).send({ success: false, msg: 'Error segment not found' });\n      }\n      Segment2MongoConverter.convert(queryLead, segment);\n  \n      queryLead[\"id_project\"] = req.projectid;\n      queryLead[\"status\"] = LeadConstants.NORMAL;\n      winston.info(\"queryLead\", queryLead);\n\n      let leads = await Lead.find(queryLead).exec();\n      var recipients = leads;\n  }\n\n  var group_id = req.body.group_id;\n  if (group_id) {\n    var group = await Group.findOne({ _id: group_id, id_project: req.projectid }).exec();\n    winston.info(\"group\", group);\n\n    if (!group) {\n      return res.status(404).send({ success: false, msg: 'Error group not found' });\n    }\n\n    var recipients = group.members;\n    // winston.info(\"members\", members);\n\n\n  }\n\n\n  winston.info(\"recipients\", recipients);\n  winston.info(\"recipients.length: \" + recipients.length);\n\n  let message = {\n    sender: req.body.sender || req.user._id, \n    senderFullname: req.body.senderFullname || req.user.fullName, \n    recipient: req.body.recipient, \n    recipientFullname: req.body.recipientFullname,\n    text: req.body.text, \n    id_project: req.projectid, // rendilo opzionale?\n    createdBy: req.user._id, \n    status:  messageStatus,\n    attributes: req.body.attributes, \n    type: req.body.type, \n    metadata: req.body.metadata, \n    language: req.body.language, \n    channel_type: MessageConstants.CHANNEL_TYPE.DIRECT, \n    channel: req.body.channel\n};\n  \n\n  if (recipients.length == 0) {\n    // return res  XXX\n  }\n\n  if (recipients.length == 1) {\n\n    // qui manca recipient?\n    message.recipient = recipients[0];\n    return messageService.save(message).then(function(savedMessage){                        \n        if (req.body.returnobject) {\n          return res.json(savedMessage);\n        } else {\n          return res.json({ success: true });\n        }\n\n      });\n  }\n\n\n  var promises = [];\n  for (const recipient of recipients) {\n  // recipients.forEach( async (recipient) => {\n\n    // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type) {\n    // var promise = messageService.create(req.body.sender || req.user._id, req.body.senderFullname || req.user.fullName, recipient, req.body.text,\n    //   req.projectid, req.user._id, messageStatus, req.body.attributes, req.body.type, req.body.metadata, req.body.language, MessageConstants.CHANNEL_TYPE.DIRECT, req.body.channel);\n\n    winston.info(\"recipient: \" + recipient);\n\n    message.recipient = recipient;\n\n    var user = await User.findOne({_id:recipient}).exec();\n    winston.info(\"user\", user);\n    \n    message.recipientFullname = user.fullName;\n\n    var promise = messageService.save(message);\n    promises.push(promise);\n    // .then(function(savedMessage){                    \n    // result.push(savedMessage);\n    // res.json(savedMessage);\n  }\n  //);\n\n  Promise.all(promises).then(function (data) {\n    if (req.body.returnobject) {\n      return res.json(data);\n    }\n  });\n\n  if (!req.body.returnobject) {\n    return res.json({ success: true });\n  }\n\n\n});\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n jobManager.run(async(data) => {\n        winston.info(\"run job here with payload\", data);\n\n        let message = data.payload.message;\n\n        // TODO cicla su segment\n        var segment_id = data.payload.segment_id;\n        if (segment_id) {\n            winston.info(\"segment_id: \"+ segment_id);\n            \n            \n          \n          \n        \n            var queryLead = {};\n\n            let segment = await Segment.findOne({id_project: data.payload.project_id, _id: segment_id }).exec();\n            if (!segment) {\n              // return res.status(404).send({ success: false, msg: 'Error segment not found' });\n              return winston.error(\"Error segment not found\");\n            }\n            Segment2MongoConverter.convert(queryLead, segment);\n        \n            queryLead[\"id_project\"] = data.payload.project_id;\n            queryLead[\"status\"] = LeadConstants.NORMAL;\n            winston.info(\"queryLead\", queryLead);\n\n\n            //const cursor = Lead.find({}).cursor();\n            //TODO RESTORE IT\n            const cursor = Lead.find(queryLead).cursor();\n\n            \n\n            // if (doc!= null) {\n            //   doc = await cursor.next()\n            // }\n\n            // cursor.next(function(error, doc) {\n            //   console.log(doc);\n\n            //   setTimout()\n            // });\n            \n\n            function sleep(ms) {\n              return new Promise((resolve) => {\n                setTimeout(resolve, ms);\n              });\n            }\n\n            for (let doc = await cursor.next(); doc != null; doc = await cursor.next() ) {\n              winston.debug(\"doc\", doc);\n\n              // if (!message.recipient) {\n                message.recipient = doc.lead_id;\n              // }\n\n              // if (!message.recipientFullname) {\n                message.recipientFullname = doc.fullname;\n              // }\n\n              winston.debug(\"message to send\", message);\n\n              messageService.save(message);\n            \n              await sleep(1000); \n              winston.debug(\"finito 1000 sec\");\n            }\n\n\n            // while(await cursor.hasNext()) {\n            //   const doc = await cursor.next();\n            //   // process doc here\n            // }\n        \n            // let leads = await Lead.find(queryLead).exec();\n            // var recipients = leads;\n            // winston.info(\"recipients\", recipients);\n\n\n        }\n\n\n    });\n\n\n\nrouter.post('/direct', async function (req, res) {\n\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var segment_id = req.body.segment_id;\n  winston.info(\"segment_id\"+ segment_id);\n\n  let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n  // winston.info(\"messageStatus\"+ messageStatus);\n\n\n  let message = {\n    sender: req.body.sender || req.user._id, \n    senderFullname: req.body.senderFullname || req.user.fullName, \n    recipient: req.body.recipient, \n    recipientFullname: req.body.recipientFullname,\n    text: req.body.text, \n    id_project: req.projectid, // rendilo opzionale?\n    createdBy: req.user._id, \n    status:  messageStatus,\n    attributes: req.body.attributes, \n    type: req.body.type, \n    metadata: req.body.metadata, \n    language: req.body.language, \n    channel_type: MessageConstants.CHANNEL_TYPE.DIRECT, \n    channel: req.body.channel\n  };\n  \n  winston.info(\"message before\", message);\n\n  jobManager.publish(\n        {segment_id: segment_id, project_id: req.projectid, message: message}\n    );\n\n    return res.json({ queued: true });\n\n\n});\n\n\n\n\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/copilot.js",
    "content": "let express = require('express');\nlet router = express.Router();\nlet winston = require('../config/winston');\nconst { Webhook } = require('../models/webhook');\nconst webhookService = require('../services/webhookService');\n\nrouter.get('/', async (req, res) => {\n\n    let id_project = req.projectid;\n    let payload = req.body;\n    let params = req.query;\n    let request_id = req.query.request_id;\n    if (!request_id) {\n        return res.status(400).send({ success: false, error: \"Missing query params request_id\" })\n    }\n    payload.request_id = request_id;\n    payload.webhook_query_params = params;\n\n    let webhooks = await Webhook.find({ id_project: id_project, copilot: true }).catch((err) => {\n        winston.error(\"Error finding copilot webhooks: \", err);\n        return res.status(500).send({ success: false, error: err });\n    })\n\n    let promises = webhooks.map((w) => \n        webhookService.run(w, payload)\n            .then((response) => {\n                return response;\n            }).catch((err) => {\n                return;\n            })\n    )\n\n    Promise.all(promises).then((result) => {\n        return res.status(200).send(result);\n    }).catch((err) => {\n        // Should never executed - check it\n        return res.status(500).send({ success: false, error: err });\n    })\n\n})\n\nrouter.post('/', async (req, res) => {\n\n    let id_project = req.projectid;\n    let payload = req.body;\n    let params = req.query;\n    let request_id = req.query.request_id;\n    if (!request_id) {\n        return res.status(400).send({ success: false, error: \"Missing query params request_id\" })\n    }\n    payload.request_id = request_id;\n    payload.webhook_query_params = params;\n\n    let webhooks = await Webhook.find({ id_project: id_project, copilot: true }).catch((err) => {\n        winston.error(\"Error finding copilot webhooks: \", err);\n        return res.status(500).send({ success: false, error: err });\n    })\n\n    let promises = webhooks.map((w) => \n        webhookService.run(w, payload)\n            .then((response) => {\n                return response;\n            }).catch((err) => {\n                return;\n            })\n    )\n\n    Promise.all(promises).then((result) => {\n        return res.status(200).send(result);\n    }).catch((err) => {\n        // Should never executed - check it\n        return res.status(500).send({ success: false, error: err });\n    })\n\n})\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/department.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Department = require(\"../models/department\");\nvar departmentService = require(\"../services/departmentService\");\n\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role');\n\nvar winston = require('../config/winston');\nvar cacheUtil = require('../utils/cacheUtil');\n\nvar departmentEvent = require(\"../event/departmentEvent\");\n\n\nrouter.post('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n  winston.debug(\"DEPT REQ BODY \", req.body);\n  var newDepartment = new Department({\n      routing: req.body.routing,\n      name: req.body.name,\n      description: req.body.description,\n      default: req.body.default,\n      status: req.body.status,\n      id_group: req.body.id_group,\n      groups: req.body.groups,\n      id_project: req.projectid,\n      createdBy: req.user.id,\n      updatedBy: req.user.id\n  });\n\n  if (req.body.id_bot) {\n      newDepartment.id_bot = req.body.id_bot;\n      newDepartment.bot_only = req.body.bot_only;\n  }\n\n\n  newDepartment.save(function (err, savedDepartment) {\n      if (err) {\n      winston.error('Error creating the department ', err);\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n      }\n      winston.debug('NEW DEPT SAVED ', savedDepartment);\n      departmentEvent.emit('department.create', savedDepartment);\n      res.json(savedDepartment);\n  });\n});\n\n\n\nrouter.put('/:departmentid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n  winston.debug(req.body);\n\n  var update = {};\n\n  // qui errore su visibile invisible\n  // if (req.body.id_bot!=undefined) {\n      update.id_bot = req.body.id_bot;\n  // }\n  if (req.body.bot_only!=undefined) {\n      update.bot_only = req.body.bot_only;\n  }\n  if (req.body.routing!=undefined) {\n      update.routing = req.body.routing;\n  }\n  if (req.body.name!=undefined) {\n      update.name = req.body.name;\n  }\n  if (req.body.description!=undefined) {\n      update.description = req.body.description;\n  }  \n  // if (req.body.id_group!=undefined) {\n      update.id_group = req.body.id_group;\n  // }\n  if (req.body.online_msg!=undefined) {\n      update.online_msg = req.body.online_msg;\n  }\n  if (req.body.status!=undefined) {\n      update.status = req.body.status;\n  }\n  if (req.body.groups!=undefined) {\n    update.groups = req.body.groups;\n  }      \n\n\n  Department.findByIdAndUpdate(req.params.departmentid, update, { new: true, upsert: true }, function (err, updatedDepartment) {\n      if (err) {\n      winston.error('Error putting the department ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n      }\n      departmentEvent.emit('department.update', updatedDepartment);\n      res.json(updatedDepartment);\n  });\n  });\n\n\n  router.patch('/:departmentid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n    winston.debug(req.body);\n  \n    var update = {};\n  \n   \n    if (req.body.status!=undefined) {\n        update.status = req.body.status;\n    }\n    if (req.body.id_bot!=undefined) {\n      update.id_bot = req.body.id_bot;\n    }\n    if (req.body.bot_only!=undefined) {\n      update.bot_only = req.body.bot_only;\n    }\n    if (req.body.routing!=undefined) {\n        update.routing = req.body.routing;\n    }\n    if (req.body.name!=undefined) {\n        update.name = req.body.name;\n    }\n    if (req.body.description!=undefined) {\n        update.description = req.body.description;\n    }  \n    if (req.body.id_group!=undefined) {\n        update.id_group = req.body.id_group;\n    }    \n    if (req.body.groups!=undefined) {\n      update.groups = req.body.groups;\n    }      \n  \n  \n    Department.findByIdAndUpdate(req.params.departmentid, update, { new: true, upsert: true }, function (err, updatedDepartment) {\n        if (err) {\n        winston.error('Error patching the department ', err);\n        return res.status(500).send({ success: false, msg: 'Error patching object.' });\n        }\n        departmentEvent.emit('department.update', updatedDepartment);\n        res.json(updatedDepartment);\n    });\n    });\n\n\n  // TODO aggiungere altro endpoint qui che calcola busy status come calculate di tiledesk-queue\n\n\nrouter.get('/:departmentid/operators', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], async (req, res) => {\n  winston.debug(\"Getting department operators req.projectid: \"+req.projectid);\n  \n  var disableWebHookCall = undefined;\n  if (req.query.disableWebHookCall) {\n    disableWebHookCall = (req.query.disableWebHookCall == 'true') ;\n  }\n\n  winston.debug(\"disableWebHookCall: \"+ disableWebHookCall);\n\n  // getOperators(departmentid, projectid, nobot) {\n\n\n    var context = {req:req};\n    var operatorsResult = await departmentService.getOperators(req.params.departmentid, req.projectid, req.query.nobot, disableWebHookCall, context);\n    winston.debug(\"Getting department operators operatorsResult\", operatorsResult);\n\n    delete operatorsResult.context;\n    return res.status(200).send(operatorsResult);\n\n});\n\n\n// ======================== ./END - GET MY DEPTS ========================\n\n// GET ALL DEPTS (i.e. NOT FILTERED FOR STATUS and WITH AUTHENTICATION (USED BY THE DASHBOARD)\nrouter.get('/allstatus', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])], function (req, res) {\n\n  // winston.debug(\"## GET ALL DEPTS req.project.isActiveSubscription \", req.project.isActiveSubscription)\n  // winston.debug(\"## GET ALL DEPTS req.project.trialExpired \", req.project.trialExpired)\n\n  // if (req.project.profile) {\n  //   winston.debug(\"## GET ALL DEPTS eq.project.profile.type \", req.project.profile.type);\n  // }\n\n  winston.debug(\"## GET ALL DEPTS req.project \", req.project)\n\n  var query = { \"id_project\": req.projectid, status: { $gte:  0 } }; // nascondi quelli con status = hidden (-1) for dashboard\n                                            //secondo me qui manca un parentesi tonda per gli or\n  if (req.project && req.project.profile && (req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false)) {\n\n    query.default = true;\n  }\n\n\n  if (req.query.sort) {\n    // return Department.find({ \"id_project\": req.projectid }).sort({ updatedAt: 'desc' }).exec(function (err, departments) {\n    // QUESTO LO COMMENTO 11.09.19 return Department.find({ \"id_project\": req.projectid }).sort({ name: 'asc' }).exec(function (err, departments) { \n      winston.debug(\"## GET ALL DEPTS QUERY (1)\", query)\n    return Department.find(query).sort({ name: 'asc' }).exec(function (err, departments) {\n\n      if (err) {\n        winston.error('Error getting the departments.', err);\n        winston.debug('Error getting the departments.', err);\n        return res.status(500).send({ success: false, msg: 'Error getting the departments.', err: err });\n      }\n\n      return res.json(departments);\n    });\n  } else {\n    winston.debug(\"## GET ALL DEPTS QUERY (1)\", query)\n    // return Department.find({ \"id_project\": req.projectid }, function (err, departments) {\n    return Department.find(query)\n    //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, req.projectid+\":departments:query:allstatus\")\n    .exec(function (err, departments) {\n      if (err) {\n        winston.error('Error getting the departments.', err);\n        return res.status(500).send({ success: false, msg: 'Error getting the departments.', err: err });\n      }\n\n      return res.json(departments);\n    });\n  }\n});\n\n\nrouter.get('/:departmentid', function (req, res) {\n  winston.debug(req.body);\n\n  let departmentid = req.params.departmentid;\n\n\n  if (departmentid == \"default\") {\n    winston.debug(\"departmentid\", departmentid);\n\n    var query = {};\n    // winston.debug(\"req.query\", req.query);\n\n    // if (req.appid) {\n    query.id_project = req.projectid;\n    query.default = true;\n    // }\n\n    winston.debug(\"query\", query);\n\n    Department.findOne(query, function (err, department) {\n      if (err) return (err);\n\n      return res.json(department);\n    });\n\n  } else {\n    Department.findById(departmentid, function (err, department) {\n      if (err) {\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      if (!department) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      res.json(department);\n    });\n  }\n\n});\n\n// router.get('/', passport.authenticate(['anonymous'], { session: false }), function (req, res) {\n\n// GET DEPTS FILTERED FOR STATUS === 1 and WITHOUT AUTHENTICATION (USED BY THE WIDGET)\n// note:THE STATUS EQUAL TO 1 CORRESPONDS TO THE DEPARTMENTS VISIBLE THE STATUS EQUAL TO 0 CORRESPONDS TO THE HIDDEN DEPARTMENTS\nrouter.get('/', function (req, res) {\n\n  winston.debug(\"req projectid\", req.projectid);\n  winston.debug(\"req.query.sort\", req.query.sort);\n\n\n  var query = { \"id_project\": req.projectid, \"status\": 1 };\n  winston.debug('GET DEPTS FILTERED FOR STATUS === 1 req.projectid ', req.projectid);\n  if (req.project && req.project.profile) {\n    winston.debug('GET DEPTS FILTERED FOR STATUS === 1 req.project.profile.type ', req.project.profile.type);\n  }\n  winston.debug('GET DEPTS FILTERED FOR STATUS === 1 req.project.profile.type ',  req.project.trialExpired);\n  winston.debug('GET DEPTS FILTERED FOR STATUS === 1 req.project.isActiveSubscription ',  req.project.isActiveSubscription);\n  \n                                            //secondo me qui manca un parentesi tonda per gli or\n  if (req.project && req.project.profile && (req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false)) {\n\n    query.default = true;\n  }\n\n  if (req.query.sort) {\n    // COMMENTO QUESTO 11.09.19 return Department.find({ \"id_project\": req.projectid, \"status\": 1 }).sort({ name: 'asc' }).exec(function (err, departments) {\n    return Department.find(query).sort({ name: 'asc' }).exec(function (err, departments) {\n\n      if (err) {\n        winston.error('Error getting the departments.', err);\n        return res.status(500).send({ success: false, msg: 'Error getting the departments.', err: err });\n      }\n\n      return res.json(departments);\n    });\n  } else {\n    return Department.find(query, function (err, departments) {\n      if (err) {\n        winston.error('Error getting the departments.', err);\n        return res.status(500).send({ success: false, msg: 'Error getting the departments.', err: err });\n      }\n\n      return res.json(departments);\n    });\n  }\n});\n\nrouter.delete('/:departmentid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.params.departmentid: \"+req.params.departmentid);\n\n  Department.findOneAndRemove({_id: req.params.departmentid}, function (err, department) {\n  // Department.remove({ _id: req.params.departmentid }, function (err, department) {\n      \n      if (err) {\n      winston.error('Error deleting the department ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n      }\n      // nn funziuona perchje nn c'è id_project\n      departmentEvent.emit('department.delete', department);\n      res.json(department);\n  });\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/email.js",
    "content": "var express = require('express');\n\nvar router = express.Router();\n\nvar emailService = require(\"../services/emailService\");\nvar winston = require('../config/winston');\nconst recipientEmailUtil = require(\"../utils/recipientEmailUtil\");\n\n\n\nrouter.get('/templates/:templateid', \n async (req, res) => {\n\n  let templateid = req.params.templateid+\".html\";\n  winston.debug(\"req.params.templateid: \"+req.params.templateid);\n  winston.debug(\"process.env.EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE: \"+process.env.EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE);\n\n  var html = await emailService.readTemplateFile(templateid);\n\n  if (req.params.templateid == \"assignedRequest\" && process.env.EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE) {\n    html = process.env.EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"assignedEmailMessage\" && process.env.EMAIL_ASSIGN_MESSAGE_EMAIL_HTML_TEMPLATE) {\n    html = process.env.EMAIL_ASSIGN_MESSAGE_EMAIL_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"pooledRequest\" && process.env.EMAIL_POOLED_REQUEST_HTML_TEMPLATE) {\n    html = process.env.EMAIL_POOLED_REQUEST_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"pooledEmailMessage\" && process.env.EMAIL_POOLED_MESSAGE_EMAIL_HTML_TEMPLATE) {\n    html = process.env.EMAIL_POOLED_MESSAGE_EMAIL_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"newMessage\" && process.env.EMAIL_NEW_MESSAGE_HTML_TEMPLATE) {\n    html = process.env.EMAIL_NEW_MESSAGE_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"ticket\" && process.env.EMAIL_TICKET_HTML_TEMPLATE) {\n    html = process.env.EMAIL_TICKET_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"newMessageFollower\" && process.env.EMAIL_FOLLOWER_HTML_TEMPLATE) {\n    html = process.env.EMAIL_FOLLOWER_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"emailDirect\" && process.env.EMAIL_DIRECT_HTML_TEMPLATE) {\n    html = process.env.EMAIL_DIRECT_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"resetPassword\" && process.env.EMAIL_RESET_PASSWORD_HTML_TEMPLATE) {\n    html = process.env.EMAIL_RESET_PASSWORD_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"passwordChanged\" && process.env.EMAIL_PASSWORD_CHANGED_HTML_TEMPLATE) {\n    html = process.env.EMAIL_PASSWORD_CHANGED_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"beenInvitedExistingUser\" && process.env.EMAIL_EXUSER_INVITED_HTML_TEMPLATE) {\n    html = process.env.EMAIL_EXUSER_INVITED_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"beenInvitedNewUser\" && process.env.EMAIL_NEWUSER_INVITED_HTML_TEMPLATE) {\n    html = process.env.EMAIL_NEWUSER_INVITED_HTML_TEMPLATE;\n  }\n\n  if (req.params.templateid == \"verify\" && process.env.EMAIL_VERIFY_HTML_TEMPLATE) {\n    html = process.env.EMAIL_VERIFY_HTML_TEMPLATE;\n  }\n  \n  if (req.params.templateid == \"sendTranscript\" && process.env.EMAIL_SEND_TRANSCRIPT_HTML_TEMPLATE) {\n    html = process.env.EMAIL_SEND_TRANSCRIPT_HTML_TEMPLATE;\n  }\n\n  \n  res.json({template:html});\n});\n \n\n\nrouter.post('/test/send', \n async (req, res) => {\n  let to = req.body.to;\n  winston.debug(\"to\",to);\n\n  let configEmail = req.body.config;\n  winston.debug(\"configEmail\", configEmail);\n\n  emailService.sendTest(to, configEmail, function(err,obj) {\n    // winston.info(\"sendTest rest\", err, obj);\n    res.json({error: err, response:obj});\n  });\n    \n});\n\n\nif (process.env.ENABLE_TEST_EMAIL_ENDPOINT==true || process.env.ENABLE_TEST_EMAIL_ENDPOINT==\"true\") {\n\n\n  router.post('/test/sendNewAssignedRequestNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n\n    emailService.sendNewAssignedRequestNotification(to, request, req.project);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n  router.post('/test/sendNewAssignedAgentMessageEmailNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n    let message = {\n      \"text\": \"text\",\n      \"request\": request\n    }\n                                                              //(to, request, project, message)\n    emailService.sendNewAssignedAgentMessageEmailNotification(to, request, req.project, message);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n\n\n  router.post('/test/sendNewPooledRequestNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n                                                              \n    emailService.sendNewPooledRequestNotification(to, request, req.project);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n\n\n  router.post('/test/sendNewPooledMessageEmailNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n    let message = {\n      \"text\": \"text\",\n      \"request\": request\n    }\n                                                              \n    emailService.sendNewPooledMessageEmailNotification(to, request, req.project, message);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n\n\n  router.post('/test/sendNewMessageNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n    let message = {\n      \"text\": \"text\",\n      \"request\": request\n    }\n                    \n    // sendNewMessageNotification(to, message, project, tokenQueryString, sourcePage)\n    emailService.sendNewMessageNotification(to, message, req.project, \"tokenQueryString\", \"sourcePage\");\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n  router.post('/test/sendEmailChannelNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n    let message = {\n      \"text\": \"text\",\n      \"request\": request\n    }\n                    \n    // (to, message, project, tokenQueryString, sourcePage) \n      emailService.sendEmailChannelNotification(to, message, req.project, \"tokenQueryString\", \"sourcePage\");\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n\n  router.post('/test/sendFollowerNotification', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\"\n    }\n    let message = {\n      \"text\": \"text\",\n      \"request\": request\n    }\n                    \n      emailService.sendFollowerNotification(to, message, req.project);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n\n  router.post('/test/sendRequestTranscript', \n  async (req, res) => {\n\n    let to = req.body.to;\n    winston.debug(\"to\",to);\n\n    let configEmail = req.body.config;\n    winston.debug(\"configEmail\", configEmail);\n\n    let request = {\n      \"_id\" : \"6316fe117c04320341200e8a\",\n      \"status\" : 50,\n      \"preflight\" : true,\n      \"hasBot\" : false,\n      \"participants\" : [],\n      \"priority\" : \"medium\",\n      \"request_id\" : \"support-group-123\",\n      \"first_text\" : \"first_text\",\n      createdAt: new Date()\n\n    }\n    let messages = [{\n      \"text\": \"text\",\n      \"request\": request,\n      createdAt: new Date()\n    }]\n\n    // sendRequestTranscript(to, messages, request, project)\n\n                    \n      emailService.sendRequestTranscript(to, messages, request, req.project);\n    \n    res.json({\"status\":\"delivering\"});\n      \n  });\n\n\n}\n\n\n\n\n//TODO add cc\nrouter.post('/internal/send', \n async (req, res) => {\n  let to = req.body.to;\n  winston.debug(\"to: \" + to);\n\n  let text = req.body.text;\n  winston.debug(\"text: \" + text);\n\n  let request_id = req.body.request_id;\n  winston.debug(\"request_id: \" + request_id);\n\n  let subject = req.body.subject;\n  winston.debug(\"subject: \" + subject);\n\n  winston.debug(\"req.project\", req.project);\n\n  let newto = await recipientEmailUtil.process(to, req.projectid);\n  winston.debug(\"newto: \" + newto);\n\n  let replyto = req.body.replyto;\n  winston.debug(\"replyto: \" + replyto);\n\n  //winston.info(\"Sending an email with text : \" + text + \" to \" + to);\n\n  let quoteManager = req.app.get('quote_manager');\n\n  //sendEmailDirect(to, text, project, request_id, subject, tokenQueryString, sourcePage, payload)\n  emailService.sendEmailDirect(newto, text, req.project, request_id, subject, undefined, undefined, undefined, replyto, quoteManager);\n  \n  res.json({\"queued\": true});\n    \n});\n\n\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/faq.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Faq = require(\"../models/faq\");\nvar Faq_kb = require(\"../models/faq_kb\");\nvar multer = require('multer')\nconst faqBotEvent = require('../event/faqBotEvent');\nvar winston = require('../config/winston');\nconst faqEvent = require('../event/faqBotEvent')\n\nvar parsecsv = require(\"fast-csv\");\nconst botEvent = require('../event/botEvent');\nconst uuidv4 = require('uuid/v4');\ncsv = require('csv-express');\ncsv.separator = ';';\nconst axios = require(\"axios\").default;\nvar configGlobal = require('../config/global');\nconst roleConstants = require('../models/roleConstants');\nconst roleChecker = require('../middleware/has-role');\nconst { ChatbotService } = require('../services/chatbotService');\n\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;\n  winston.debug(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.debug(\"Max upload file size is infinity\");\n}\nvar upload = multer({limits: uploadlimits});\n\n\n// POST CSV FILE UPLOAD FROM CLIENT\nrouter.post('/uploadcsv', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), upload.single('uploadFile'), function (req, res, next) {\n  winston.debug(' -> -> REQ BODY ', req.body);\n  winston.debug(' -> ID FAQ-KB  ', req.body.id_faq_kb);\n  winston.debug(' -> DELIMITER ', req.body.delimiter);\n  winston.debug(' -> FILE ', req.file);\n\n  var id_faq_kb = req.body.id_faq_kb;\n  winston.debug('id_faq_kb: ' + id_faq_kb);\n\n  var delimiter = req.body.delimiter || \";\";\n  winston.debug('delimiter: ' + delimiter);\n\n  var csv = req.file.buffer.toString('utf8');\n  winston.debug(\"--> csv: \", csv)\n  // winston.debug(' -> CSV STRING ', csv);\n\n  // res.json({ success: true, msg: 'Importing CSV...' });\n\n  // PARSE CSV\n\n\n\n  Faq_kb.findById(id_faq_kb).exec(function (err, faq_kb) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug('faq_kb ', faq_kb.toJSON());\n\n    // getFaqKbKeyById(req.body.id_faq_kb, function (remote_faqkb_key) {\n\n    parsecsv.parseString(csv, { headers: false, delimiter: delimiter })\n      .on(\"data\", function (data) {\n        winston.debug('PARSED CSV ', data);\n\n        winston.debug('--> PARSED CSV ', data);\n\n        var question = data[0]\n        //var answer = data[1]\n        var intent_id = data[2];\n        var intent_display_name = data[3];\n        var webhook_enabled = data[4];\n\n\n        var actions = [\n          {\n            _tdActionType: \"reply\",\n            _tdActionId: uuidv4(),\n            text: data[1],\n            attributes: {\n              commands: [\n                {\n                  type: \"wait\",\n                  time: 500\n                },\n                {\n                  type: \"message\",\n                  message: {\n                    type: \"text\",\n                    text: data[1]\n                  }\n                }\n              ]\n            }\n\n          }\n        ]\n\n        var webhook_enabled_boolean = false;\n        if (webhook_enabled) {\n          webhook_enabled_boolean = (webhook_enabled == 'true');\n        }\n        // var row = {question: element.question, answer: element.answer, \n        //   intent_id: element.intent_id, intent_display_name: element.intent_display_name,\n        //   webhook_enabled: element.webhook_enabled || false }\n\n        var newFaq = new Faq({\n          id_faq_kb: id_faq_kb,\n          question: question,\n          //answer: answer,\n          actions: actions,\n          intent_id: intent_id,\n          intent_display_name: intent_display_name,\n          webhook_enabled: webhook_enabled_boolean,\n          language: faq_kb.language,\n          id_project: req.projectid,\n          createdBy: req.user.id,\n          updatedBy: req.user.id\n        });\n\n        winston.debug(\"--> newFaq: \", JSON.stringify(newFaq, null, 2));\n\n        newFaq.save(function (err, savedFaq) {\n          if (err) {\n            winston.error('--- > ERROR uploadcsv', err)\n\n            // return res.status(500).send({ success: false, msg: 'Error saving object.' }); // ADDED 24 APR\n          } else {\n            faqBotEvent.emit('faq.create', savedFaq);\n          }\n\n        });\n      })\n      .on(\"end\", function () {\n        winston.debug(\"PARSE DONE\");\n        //faqBotEvent.emit('faq_train.create', id_faq_kb)\n        res.json({ success: true, msg: 'CSV Parsed' });\n      })\n      .on(\"error\", function (err) {\n        winston.error(\"PARSE ERROR uploadcsv\", err);\n        res.json({ success: false, msg: 'Parsing error' });\n      });\n  });\n});\n\n\nrouter.post('/', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(req.body);\n\n  Faq_kb.findById(req.body.id_faq_kb).exec(function (err, faq_kb) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug('faq_kb ', faq_kb.toJSON());\n\n    var newFaq = new Faq({\n      _id: req.body._id,\n      id_faq_kb: req.body.id_faq_kb,\n      question: req.body.question,\n      answer: req.body.answer,\n      reply: req.body.reply,\n      form: req.body.form,\n      enabled: true,\n      id_project: req.projectid,\n      topic: req.body.topic,\n      language: faq_kb.language,\n      webhook_enabled: req.body.webhook_enabled,\n      intent_display_name: req.body.intent_display_name,\n      createdBy: req.user.id,\n      updatedBy: req.user.id\n    });\n\n    if (req.body.enabled != undefined) {\n      newFaq.enabled = req.body.enabled;\n    }\n    if (req.body.actions) {\n      newFaq.actions = req.body.actions\n    }\n    if (req.body.attributes) {\n      newFaq.attributes = req.body.attributes\n    }\n    if (req.body.intent_id) {\n      newFaq.intent_id = req.body.intent_id;\n    }\n\n    newFaq.save(function (err, savedFaq) {\n      if (err) {\n        if (err.code == 11000) {\n          return res.status(409).send({ success: false, msg: 'Duplicate  intent_display_name.' });\n        } else {\n          winston.debug('--- > ERROR ', err)\n          return res.status(500).send({ success: false, msg: 'Error saving object.' });\n        }\n      }\n      winston.debug('1. ID OF THE NEW FAQ CREATED ', savedFaq._id)\n      winston.debug('1. QUESTION OF THE NEW FAQ CREATED ', savedFaq.question)\n      winston.debug('1. ANSWER OF THE NEW FAQ CREATED ', savedFaq.answer)\n      winston.debug('1. ID FAQKB GET IN THE OBJECT OF NEW FAQ CREATED ', savedFaq.id_faq_kb);\n\n      faqBotEvent.emit('faq.create', savedFaq);\n      //faqBotEvent.emit('faq_train.create', req.body.id_faq_kb)\n\n      res.json(savedFaq);\n\n\n    });\n  });\n});\n\nrouter.post('/ops_update', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), async (req, res) => {\n\n  let id_faq_kb = req.body.id_faq_kb;\n  let operations = req.body.operations;\n  let chatbotService = new ChatbotService();\n\n  for (let op of operations) {\n    let HTTPREQUEST;\n    let id;\n\n    // method post\n    switch (op.type) {\n      case 'post':\n        HTTPREQUEST = {\n          url: apiUrl + '/' + req.projectid + '/faq/',\n          headers: {\n            'Content-Type': 'application/json',\n            'Authorization': req.headers.authorization\n          },\n          json: op.intent,\n          method: 'post'\n        }\n        winston.debug(\"operation HTTPREQUEST: \", HTTPREQUEST);\n        myrequest(\n          HTTPREQUEST, async (err, resbody) => {\n            if (err) {\n              winston.error(\"err performing operation: \", err);\n            } else {\n              winston.debug(\"\\n\\nresbody operation: \", resbody);\n              chatbotService.setModified(id_faq_kb, true)\n            }\n          }\n        )\n        break;\n\n      // method put\n      case 'put':\n        id = op.intent._id;\n        if (op.intent.intent_id) {\n          id = \"intentId\" + op.intent.intent_id;\n        }\n        HTTPREQUEST = {\n          url: apiUrl + '/' + req.projectid + '/faq/' + id,\n          headers: {\n            'Content-Type': 'application/json',\n            'Authorization': req.headers.authorization\n          },\n          json: op.intent,\n          method: 'put'\n        }\n        winston.debug(\"operation HTTPREQUEST: \", HTTPREQUEST);\n        myrequest(\n          HTTPREQUEST, async (err, resbody) => {\n            if (err) {\n              winston.error(\"err performing operation: \", err);\n            } else {\n              winston.debug(\"\\n\\nresbody operation: \", resbody);\n              chatbotService.setModified(id_faq_kb, true)\n            }\n          }\n        )\n        break;\n\n      // method patch\n      case 'patch':\n        HTTPREQUEST = {\n          url: apiUrl + '/' + req.projectid + '/faq/' + op.intent._id + '/attributes',\n          headers: {\n            'Content-Type': 'application/json',\n            'Authorization': req.headers.authorization\n          },\n          json: op.intent.attributes,\n          method: 'patch'\n        }\n        winston.debug(\"operation HTTPREQUEST: \", HTTPREQUEST);\n        myrequest(\n          HTTPREQUEST, async (err, resbody) => {\n            if (err) {\n              winston.error(\"err performing operation: \", err);\n            } else {\n              winston.debug(\"\\n\\nresbody operation: \", resbody);\n              chatbotService.setModified(id_faq_kb, true)\n            }\n          }\n        )\n        break;\n\n      // method delete\n      case 'delete':\n        id = op.intent._id;\n        if (op.intent.intent_id) {\n          id = \"intentId\" + op.intent.intent_id;\n        }\n        HTTPREQUEST = {\n          url: apiUrl + '/' + req.projectid + '/faq/' + id + '?id_faq_kb=' + id_faq_kb,\n          headers: {\n            'Content-Type': 'application/json',\n            'Authorization': req.headers.authorization\n          },\n          method: 'delete'\n        }\n        winston.debug(\"operation HTTPREQUEST: \", HTTPREQUEST);\n        myrequest(\n          HTTPREQUEST, async (err, resbody) => {\n            if (err) {\n              winston.error(\"err performing operation: \", err);\n            } else {\n              winston.debug(\"\\n\\nresbody operation: \", resbody);\n              chatbotService.setModified(id_faq_kb, true)\n            }\n          }\n        )\n        break;\n    }\n  }\n\n  res.status(200).send({ success: true });\n\n\n})\n\nrouter.patch('/:faqid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n  let data = req.body;\n  winston.debug(\"data: \", data);\n\n  // aggiugnere controllo su intent_id qui\n\n  Faq.findById(req.params.faqid, function (err, updatedFaq) {\n    if (err) {\n      winston.error('Find Faq by id ERROR: ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    if (!updatedFaq) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n\n    if (!updatedFaq.attributes) {\n      winston.debug(\"empty attributes\");\n      winston.debug(\"empty attributes\");\n      updatedFaq.attributes = {};\n    }\n\n    winston.debug(\"updatedFaq attributes\", updatedFaq.attributes);\n\n    Object.keys(data).forEach(function (key) {\n      var val = data[key];\n      winston.debug(\"data attributes\" + key + \" \" + val);\n      updatedFaq.attributes[key] = val;\n    })\n\n    winston.debug(\"updatedFaq: \", updatedFaq);\n    winston.debug(\"updatedFaq attributes: \", updatedFaq.attributes);\n\n    winston.debug(\"updatedBot attributes\", updatedFaq.attributes)\n\n    updatedFaq.markModified('attributes');\n\n    //cache invalidation\n    updatedFaq.save(function (err, savedFaq) {\n      if (err) {\n        winston.error(\"saving faq attributes ERROR: \", err);\n        return res.status(500).send({ success: false, msg: 'Error saving object.' });\n      }\n\n      winston.debug(\"saved faq attributes\", savedFaq.toObject());\n\n      winston.verbose(\"saved faq attributes\", savedFaq.toObject());\n      faqBotEvent.emit('faq.update', savedFaq);\n      res.json(savedFaq);\n    })\n  })\n})\n\nrouter.put('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug('UPDATE FAQ ', req.body);\n  let faqid = req.params.faqid;\n\n  if (!req.body.id_faq_kb) {\n    return res.status(422).send({ err: \"Missing id_faq_kb in Request Body\" })\n  }\n  let id_faq_kb = req.body.id_faq_kb;\n\n  var update = {};\n\n  if (req.body.intent != undefined) {\n    update.intent = req.body.intent;\n  }\n  if (req.body.question != undefined) {\n    update.question = req.body.question;\n  }\n  if (req.body.answer != undefined) {\n    update.answer = req.body.answer;\n  }\n  if (req.body.topic != undefined) {\n    update.topic = req.body.topic;\n  }\n  if (req.body.status != undefined) {\n    update.status = req.body.status;\n  }\n  if (req.body.language != undefined) {\n    update.language = req.body.language;\n  }\n  if (req.body.intent_display_name != undefined) {\n    update.intent_display_name = req.body.intent_display_name;\n  }\n  if (req.body.webhook_enabled != undefined) {\n    update.webhook_enabled = req.body.webhook_enabled;\n  }\n  if (req.body.enabled != undefined) {\n    update.enabled = req.body.enabled;\n  }\n  if (req.body.reply != undefined) {\n    update.reply = req.body.reply;\n  }\n  if (req.body.form != undefined) {\n    update.form = req.body.form;\n  }\n  if (req.body.actions != undefined) {\n    update.actions = req.body.actions;\n  }\n  if (req.body.attributes != undefined) {\n    update.attributes = req.body.attributes;\n  }\n  if (req.body.agents_available != undefined) {\n    update.agents_available = req.body.agents_available;\n  }\n\n  if (faqid.startsWith(\"intentId\")) {\n    let intent_id = faqid.substring(8);\n    //Faq.findOneAndUpdate({ id_faq_kb: id_faq_kb, intent_id: intent_id }, update, { new: true, upsert: true }, (err, updatedFaq) => {\n    Faq.findOneAndUpdate({ id_faq_kb: id_faq_kb, intent_id: intent_id }, update, { new: true }, (err, updatedFaq) => {\n      if (err) {\n        if (err.code == 11000) {\n          return res.status(409).send({ success: false, msg: 'Duplicate  intent_display_name.' });\n        } else {\n          return res.status(500).send({ success: false, msg: 'Error updating object.' });\n        }\n      }\n\n      faqBotEvent.emit('faq.update', updatedFaq);\n      //faqBotEvent.emit('faq_train.update', updatedFaq.id_faq_kb);\n\n      res.status(200).send(updatedFaq);\n    })\n\n  } else {\n\n    Faq.findByIdAndUpdate(req.params.faqid, update, { new: true, upsert: true }, function (err, updatedFaq) {\n      if (err) {\n        if (err.code == 11000) {\n          return res.status(409).send({ success: false, msg: 'Duplicate  intent_display_name.' });\n        } else {\n          return res.status(500).send({ success: false, msg: 'Error updating object.' });\n        }\n      }\n\n      faqBotEvent.emit('faq.update', updatedFaq);\n      //faqBotEvent.emit('faq_train.update', updatedFaq.id_faq_kb);\n\n      res.status(200).send(updatedFaq);\n      // updateRemoteFaq(updatedFaq)\n    });\n  }\n\n});\n\n\n// DELETE REMOTE AND LOCAL FAQ\nrouter.delete('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug('DELETE FAQ - FAQ ID ', req.params.faqid);\n\n  let faqid = req.params.faqid;\n  let id_faq_kb;\n  if (req.query && req.query.id_faq_kb) {\n    id_faq_kb = req.query.id_faq_kb;\n  }\n\n  if (faqid.startsWith(\"intentId\")) {\n    let intent_id = faqid.substring(8);\n    if (!id_faq_kb) {\n      return res.status(500).send({ success: false, msg: \"Unable to delete object. Query param 'id_faq_kb' is mandatory if you want to delete via intent_id\" })\n    }\n\n    Faq.findOneAndDelete({ intent_id: intent_id, id_faq_kb: id_faq_kb }, (err, faq) => {\n      if (err) {\n        return res.status(500).send({ success: false, msg: \"Error deleting object.\" });\n      }\n\n      if (!faq) {\n        return res.status(404).send({ success: false, msg: \"Error deleting object. The object does not exists.\" })\n      }\n\n      winston.debug('Deleted FAQ ', faq);\n\n      faqBotEvent.emit('faq.delete', faq);\n      //faqBotEvent.emit('faq_train.delete', faq.id_faq_kb);\n\n      res.status(200).send(faq);\n\n    })\n\n  } else {\n    Faq.findByIdAndRemove({ _id: req.params.faqid }, function (err, faq) {\n      if (err) {\n        return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n      }\n      winston.debug('Deleted FAQ ', faq);\n\n      faqBotEvent.emit('faq.delete', faq);\n      //faqBotEvent.emit('faq_train.delete', faq.id_faq_kb);\n\n      res.status(200).send(faq);\n\n    });\n  }\n});\n\n// EXPORT FAQ TO CSV\nrouter.get('/csv', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n  var query = {};\n\n  winston.debug('req.query', req.query);\n\n  if (req.query.id_faq_kb) {\n    query.id_faq_kb = req.query.id_faq_kb;\n  }\n\n  winston.debug('EXPORT FAQS TO CSV QUERY', query);\n\n  Faq.find(query, 'question answer intent_id intent_display_name webhook_enabled -_id').lean().exec(function (err, faqs) {\n    if (err) {\n      winston.debug('EXPORT FAQS TO CSV ERR', err)\n      return (err)\n    };\n    var csv = [];\n    faqs.forEach(function (element) {\n      var row = {\n        question: element.question, answer: element.answer,\n        intent_id: element.intent_id, intent_display_name: element.intent_display_name,\n        webhook_enabled: element.webhook_enabled || false\n      }\n      csv.push(row);\n    });\n    winston.debug('EXPORT FAQ TO CSV FAQS', csv)\n    res.csv(csv, true)\n    // res.json(faq);\n  });\n\n});\n\n\nrouter.get('/:faqid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(req.body);\n\n  Faq.findById(req.params.faqid, function (err, faq) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(faq);\n  });\n});\n\n\nrouter.get('/', roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription']), function (req, res, next) {\n  var query = {};\n\n  winston.debug(\"GET ALL FAQ OF THE BOT ID (req.query): \", req.query);\n\n  let restricted_mode;\n\n  let project_user = req.projectuser;\n  if (project_user && project_user.role === roleConstants.AGENT) {\n    restricted_mode = true;\n  }\n\n  if (req.query.id_faq_kb) {\n    query.id_faq_kb = req.query.id_faq_kb;\n  }\n\n  var limit = 3000; // Number of request per page\n\n  if (req.query.limit) {\n    limit = parseInt(req.query.limit);\n    winston.debug('faq ROUTE - limit: ' + limit);\n  }\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('faq ROUTE - SKIP PAGE ', skip);\n\n\n\n  if (req.query.text) {\n    winston.debug(\"GET FAQ req.projectid\", req.projectid);\n\n    // query.$text = req.query.text;\n    query.$text = { \"$search\": req.query.text };\n    query.id_project = req.projectid\n  }\n\n  if (req.query.intent_display_name) {\n    query.intent_display_name = req.query.intent_display_name\n  }\n\n  if (restricted_mode) {\n    query.agents_available = {\n      $in: [ null, true ]\n    }\n  }\n\n\n  winston.debug(\"GET FAQ query\", query);\n\n  // query.$text = {\"$search\": \"question\"};\n\n  // TODO ORDER BY SCORE\n  // return Faq.find(query,  {score: { $meta: \"textScore\" } }) \n  // .sort( { score: { $meta: \"textScore\" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n\n  // in testing...\n  // return Faq.search('a closer', (err, result) => {\n  //   console.log(\"result: \", result);\n  // })\n\n  return Faq.find(query)\n    .skip(skip).limit(limit)\n    .populate({ path: 'faq_kb' })\n    .lean()\n    .exec(function (err, faqs) {\n\n      winston.debug(\"GET FAQ \", faqs);\n\n      if (err) {\n        winston.debug('GET FAQ err ', err)\n        return next(err)\n      };\n\n      if (restricted_mode === true) {\n        faqs = faqs.map(({ webhook_enabled, faq_kb, actions, attributes, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)\n        faqs = faqs.filter(f => (f.intent_display_name !== \"start\" && f.intent_display_name !== 'defaultFallback'));\n      }\n\n      res.status(200).send(faqs);\n    });\n\n});\n\nasync function myrequest(options, callback) {\n\n  winston.debug(\"myrequest options: \", options)\n  return await axios({\n    url: options.url,\n    method: options.method,\n    data: options.json,\n    params: options.params,\n    headers: options.headers\n  }).then((res) => {\n    if (res && res.status == 200 && res.data) {\n      if (callback) {\n        callback(null, res.data);\n      }\n    }\n    else {\n      if (callback) {\n        callback(TiledeskClient.getErr({ message: \"Response status not 200\" }, options, res), null, null);\n      }\n    }\n  }).catch((err) => {\n    if (callback) {\n      callback(err, null, null);\n    }\n  })\n}\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/faq_kb.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Faq_kb = require(\"../models/faq_kb\");\nvar Faq = require(\"../models/faq\");\nvar Department = require(\"../models/department\");\nvar faqService = require(\"../services/faqService\");\nconst botEvent = require('../event/botEvent');\nconst faqBotEvent = require('../event/faqBotEvent');\nvar winston = require('../config/winston');\nvar httpUtil = require(\"../utils/httpUtil\");\nconst { forEach } = require('lodash');\nvar multer = require('multer')\nvar configGlobal = require('../config/global');\nconst faq = require('../models/faq');\nvar jwt = require('jsonwebtoken');\nconst uuidv4 = require('uuid/v4');\nconst trainingService = require('../services/trainingService');\nvar roleChecker = require('../middleware/has-role');\nconst roleConstants = require('../models/roleConstants');\nconst errorCodes = require('../errorCodes');\nconst faq_kb = require('../models/faq_kb');\n\nlet chatbot_templates_api_url = process.env.CHATBOT_TEMPLATES_API_URL\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = { fileSize: parseInt(MAX_UPLOAD_FILE_SIZE) };\n  winston.debug(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.debug(\"Max upload file size is infinity\");\n}\nvar upload = multer({ limits: uploadlimits });\n\n\nrouter.post('/', roleChecker.hasRole('admin'), async function (req, res) {\n  winston.debug('create BOT ', req.body);\n\n  let quoteManager = req.app.get('quote_manager');\n  let limits = await quoteManager.getPlanLimits(req.project);\n  let chatbots_limit = limits.chatbots;\n  winston.debug(\"chatbots_limit for project \" + req.projectid + \": \" + chatbots_limit);\n\n  let chatbots_count = await Faq_kb.countDocuments({ id_project: req.projectid, type: { $ne: \"identity\" } }).exec();\n  winston.debug(\"chatbots_count for project \" + req.projectid + \": \" + chatbots_count);\n\n  if (chatbots_count >= chatbots_limit) {\n    winston.info(\"Chatbots limit reached for project \" + req.projectid + \". Block currently disabled.\");\n    //return res.status(403).send({ success: false, error: \"Maximum number of chatbots reached for the current plan\", plan_limit: chatbots_limit })\n  }\n\n  faqService.create(req.projectid, req.user.id, req.body).then((savedFaq_kb) => {\n    res.status(200).send(savedFaq_kb);\n  }).catch((err) => {\n    res.status(500).send({ succes: false, error: err })\n  })\n\n});\n\nrouter.post('/train', roleChecker.hasRole('admin'), function (req, res) {\n\n  winston.info('train BOT ', req.body);\n\n  Faq_kb.findById(req.body.id_faq_kb).exec(function (err, faq_kb) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug('faq_kb ', faq_kb.toJSON());\n\n    winston.debug('faq_kb.type :' + faq_kb.type);\n    if (faq_kb.type == \"internal\" && faq_kb.url) {\n\n      var train = {\n        language: faq_kb.language,\n        nlu: []\n      };\n      winston.info(\"train\", train);\n\n      var query = { \"id_project\": req.projectid, \"id_faq_kb\": req.body.id_faq_kb };\n\n      Faq.find(query)\n        .limit(10000)\n        .lean().\n        exec(async (err, faqs) => {\n          if (err) {\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          if (faqs && faqs.length > 0) {\n            winston.info(\"faqs exact\", faqs);\n\n            faqs.forEach(function (f) {\n              var intent = {\n                intent: f.intent_display_name,\n                examples: []\n              }\n              var questions = f.question.split(\"\\n\");\n              winston.info(\"questions\", questions);\n\n              questions.forEach(function (q) {\n                winston.info(\"q\", q);\n                intent.examples.push(q);\n              });\n              winston.info(\"intent\", intent);\n              train.nlu.push(intent);\n            });\n            winston.info(\"train\", train);\n\n            try {\n              var trainHttp = await httpUtil.call(faq_kb.url + \"/trainandload\", undefined, train, \"POST\");\n            } catch (e) {\n              winston.error(\"error training\", e);\n            }\n\n            return res.json({ train: train, httpResponse: trainHttp });\n\n          } else {\n            return res.status(400).send({ success: false, msg: 'no faq to  train on external bot.' });\n          }\n        });\n    } else {\n      winston.debug('external query: ');\n      return res.status(400).send({ success: false, msg: 'you can train a standard internal bot or an external bot.' });\n    }\n\n  });\n});\n\nrouter.post('/aitrain/', roleChecker.hasRole('admin'), async (req, res) => {\n\n  const chatbot_id = req.body.id_faq_kb;\n  const id_project = req.projectid;\n  let webhook_enabled = req.body.webhook_enabled;\n\n  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project}, async (err, chatbot) => {\n    if (err) {\n      return res.status(400).send({ success: false, error: err })\n    }\n    if (!chatbot) {\n      return res.status(404).send({ sucess: false, error: \"Chatbot not found\" });\n    }\n    if (chatbot.intentsEngine === 'tiledesk-ai') {\n\n      // Option 1: emit event\n      //faqBotEvent.emit('faq_train.train', chatbot_id, webhook_enabled);\n\n      // Option 2: call service directly\n      trainingService.train(null, chatbot_id, webhook_enabled).then((training_result) => {\n        winston.info(\"training result: \", training_result);\n        let response = {\n          succes: true,\n          message: \"Training started\"\n        }\n        if (webhook_enabled === false) {\n          response.queue_message = training_result;\n        }\n        return res.status(200).send(response);\n\n      }).catch((err) => {\n        winston.error(\"training error: \", err);\n        return res.status(200).send({ success: false, message: \"Trained not started\", error: err });\n      })\n\n    } else {\n      return res.status(200).send({ success: true, message: \"Trained not started\", reason: \"Training available for intentsEngine equal to tiledesk-ai only\" })\n    }\n  })\n})\n\nrouter.post('/askbot', roleChecker.hasRole('admin'), function (req, res) {\n\n  winston.debug('ASK BOT ', req.body);\n\n  const chatbot_id = req.body.id_faq_kb;\n  const id_project = req.projectid;\n\n  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).exec(function (err, faq_kb) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug('faq_kb ', faq_kb.toJSON());\n    winston.debug('faq_kb.type :' + faq_kb.type);\n    if (faq_kb.type == \"internal\" || faq_kb.type == \"tilebot\") {\n\n      var query = { \"id_project\": id_project, \"id_faq_kb\": chatbot_id, \"question\": req.body.question };\n\n      Faq.find(query)\n        .lean().\n        exec(function (err, faqs) {\n          if (err) {\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          if (faqs && faqs.length > 0) {\n            winston.debug(\"faqs exact\", faqs);\n\n            faqs.forEach(f => {\n              f.score = 100;\n            });\n            var result = { hits: faqs };\n\n            res.json(result);\n          } else {\n            query = { \"id_project\": req.projectid, \"id_faq_kb\": req.body.id_faq_kb };\n\n            var search_obj = { \"$search\": req.body.question };\n\n            if (faq_kb.language) {\n              search_obj[\"$language\"] = faq_kb.language;\n            }\n            query.$text = search_obj;\n            winston.debug(\"fulltext search query\", query);\n            winston.debug('internal ft query: ' + query);\n\n            Faq.find(query, { score: { $meta: \"textScore\" } })\n              .sort({ score: { $meta: \"textScore\" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n              .lean().\n              exec(function (err, faqs) {\n                if (err) {\n                  winston.error('Error getting object.', err);\n                  return res.status(500).send({ success: false, msg: 'Error getting fulltext object.' });\n                }\n\n                winston.debug(\"faqs\", faqs);\n\n                var result = { hits: faqs };\n                res.json(result);\n              });\n          }\n        });\n    } else {\n      winston.debug('external query: ');\n      return res.status(400).send({ success: false, msg: 'askbot on external bot.' });\n    }\n  });\n});\n\nrouter.put('/:faq_kbid/publish', roleChecker.hasRole('admin'), async (req, res) => {\n\n  let id_faq_kb = req.params.faq_kbid;\n  winston.debug('id_faq_kb: ' + id_faq_kb);\n\n  let chatbot_id;\n  let release_note;\n\n  if (req.body.restore_from) {\n    chatbot_id = req.body.restore_from;\n    release_note = \"Restored from \" + chatbot_id;\n  } else {\n    chatbot_id = id_faq_kb;\n    release_note = req.body.release_note || \"No comment\";\n  }\n\n  const api_url = process.env.API_URL || configGlobal.apiUrl;\n  winston.debug(\"fork --> base_url: \" + api_url); // check if correct\n\n  let current_project_id = req.projectid;\n  winston.debug(\"current project id: \" + current_project_id);\n\n  let token = req.headers.authorization;\n\n  let cs = req.app.get('chatbot_service')\n\n  try {\n    //  fork(id_faq_kb, api_url, token, project_id)\n    let forked = await cs.fork(chatbot_id, api_url, token, current_project_id);\n    // winston.debug(\"forked: \", forked)\n\n    let forkedChatBotId = forked.bot_id;\n    winston.debug(\"forkedChatBotId: \" + forkedChatBotId);\n\n    let updatedForkedChabot = await Faq_kb.findByIdAndUpdate(forkedChatBotId, { $unset: { slug: 1 }, trashed: true, root_id: id_faq_kb, release_note: release_note, publishedBy: req.user.id, publishedAt: new Date().getTime() }, { new: true, upsert: true }).exec();\n    //let updatedForkedChabot = await Faq_kb.findByIdAndUpdate(forkedChatBotId, { $unset: { slug: 1 }, trashed: true, root_id: id_faq_kb, publishedBy: req.user.id, publishedAt: new Date().getTime() }, { new: true }).exec();\n    winston.debug(\"updatedForkedChabot: \", updatedForkedChabot);\n    botEvent.emit('faqbot.update', updatedForkedChabot);\n\n    const port = process.env.PORT || '3000';\n    let TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/ext/\";;\n    if (process.env.TILEBOT_ENDPOINT) {\n      TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/ext/\"\n    }\n    winston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\n    let updatedOriginalChabot = await Faq_kb.findByIdAndUpdate(id_faq_kb, { url: TILEBOT_ENDPOINT + forkedChatBotId }, { new: true, upsert: true }).exec();\n    winston.debug(\"updatedOriginalChabot: \", updatedOriginalChabot);\n\n    cs.setModified(id_faq_kb, false);\n\n    botEvent.emit('faqbot.update', updatedOriginalChabot);\n\n    return res.status(200).send({ message: \"Chatbot published successfully\", bot_id: forkedChatBotId });\n\n  } catch (e) {\n    winston.error(\"Error Unable publish chatbot: \", e);\n    return res.status(500).send({ success: false, message: \"Unable publish chatbot\" });\n  }\n});\n\nrouter.put('/:faq_kbid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(req.body);\n\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  var update = {};\n  const allowedFields = [\n    'name',\n    'description',\n    'url',\n    'webhook_url',\n    'webhook_enabled',\n    'type',\n    'trashed',\n    'public',\n    'certified',\n    'mainCategory',\n    'intentsEngine',\n    'tags',\n    'trained',\n    'short_description',\n    'title',\n    'certifiedTags',\n    'agents_available',\n    'slug'\n  ];\n\n  allowedFields.forEach(f => {\n    if (req.body[f] !== undefined) {\n      update[f] = req.body[f];\n    }\n  });\n\n  update.modified = true;\n  \n  winston.debug(\"update\", update);\n\n  Faq_kb.findOneAndUpdate({ _id: chatbot_id, id_project: id_project }, update, { new: true }, function (err, updatedFaq_kb) {   //TODO add cache_bot_here\n    if (err) {\n      if (err.code === 11000 && err.keyValue.slug) {\n        return res.status(500).send({ success: false, msg: 'Error updating object.', error: 'Slug already exists: ' + err.keyValue.slug, error_code: errorCodes.CHATBOT.ERRORS.DUPLICATE_SLUG })\n      } else {\n        return res.status(500).send({ success: false, msg: 'Error updating object.' });\n      }\n    }\n\n    if (!updatedFaq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n\n    botEvent.emit('faqbot.update', updatedFaq_kb);\n    res.json(updatedFaq_kb);\n  });\n});\n\nrouter.put('/:faq_kbid/language/:language', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), (req, res) => {\n\n  winston.debug(\"update language: \", req.params);\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  let update = {};\n  if (req.params.language != undefined) {\n    update.language = req.params.language;\n  }\n\n  winston.debug(\"update\", update);\n  Faq_kb.findOneAndUpdate({ _id: chatbot_id, id_project: id_project }, update, { new: true }, (err, updatedFaq_kb) => {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    if (!updatedFaq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n\n    Faq.updateMany({ id_faq_kb: req.params.faq_kbid }, update, (err, result) => {\n      if (err) {\n        botEvent.emit('faqbot.update', updatedFaq_kb);\n        return res.status(500).send({ success: false, msg: 'Error updating multiple object.' });\n      }\n      return res.status(200).send(updatedFaq_kb)\n    })\n\n  })\n\n})\n\nrouter.patch('/:faq_kbid/attributes', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {   //TODO add cache_bot_here\n  var data = req.body;\n\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  // TODO use service method\n  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }, function (err, updatedBot) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    if (!updatedBot) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n    \n    if (!updatedBot.attributes) {\n      winston.debug(\"empty attributes\")\n      updatedBot.attributes = {};\n    }\n\n    winston.debug(\" updatedBot attributes\", updatedBot.attributes)\n\n    Object.keys(data).forEach(function (key) {\n      var val = data[key];\n      winston.debug(\"data attributes \" + key + \" \" + val)\n      updatedBot.attributes[key] = val;\n    });\n\n    winston.debug(\"updatedBot attributes\", updatedBot.attributes)\n\n    // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n    updatedBot.markModified('attributes');\n\n    //cacheinvalidation\n    updatedBot.save(function (err, savedProject) {\n      if (err) {\n        winston.error(\"error saving bot attributes\", err)\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      winston.verbose(\" saved bot attributes\", updatedBot.toObject())\n      botEvent.emit('faqbot.update', updatedBot);\n      res.json(updatedBot);\n    });\n  });\n\n});\n\nrouter.delete('/:faq_kbid', roleChecker.hasRole('admin'), function (req, res) {\n\n  winston.debug(req.body);\n\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  Faq_kb.findOneAndDelete({ _id: chatbot_id, id_project: id_project }, function (err, faq_kb) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n    /**\n     * WARNING: faq_kb is the operation result, not the faq_kb object. The event subscriber will not receive the object as expected.\n     */\n    botEvent.emit('faqbot.delete', faq_kb);\n    res.status(200).send({ success: true, message: \"Chatbot with id \" + req.params.faq_kbid + \" deleted successfully\" })\n  });\n});\n\nrouter.get('/:faq_kbid', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(req.query);\n\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }, function (err, faq_kb) {   //TODO add cache_bot_here\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n\n    if (req.query.departmentid) {\n\n      winston.debug(\"»»» »»» req.query.departmentid\", req.query.departmentid);\n\n      Department.findById(req.query.departmentid, function (err, department) {\n        if (err) {\n          winston.error(err);\n          // return res.status(500).send({ success: false, msg: 'Error getting department.' });\n          res.json(faq_kb);\n        }\n        if (!department) {\n          winston.debug(\"Department not found\", req.query.departmentid);\n          // return res.status(404).send({ success: false, msg: 'Department not found.' });\n          res.json(faq_kb);\n        } else {\n          winston.debug(\"department\", department);\n\n          // https://github.com/Automattic/mongoose/issues/4614\n          faq_kb._doc.department = department;\n          winston.debug(\"faq_kb\", faq_kb);\n\n          res.json(faq_kb);\n        }\n      });\n\n    } else {\n      winston.debug('¿¿ MY USECASE ?? ')\n      res.json(faq_kb);\n    }\n\n  });\n});\n\nrouter.get('/:faq_kbid/published', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), async function (req, res) {\n\n  const id_project = req.projectid;\n  const chatbot_id = req.params.faq_kbid;\n\n  let published_chatbots = await faq_kb.find({ id_project: id_project, root_id: chatbot_id })\n    .sort({ publishedAt: -1 })\n    .limit(100)\n    .populate('publishedBy', '_id firstname lastname email')\n    .catch((err) => {\n      winston.error(\"Error finding published chatbots: \", err);\n      return res.status(500).send({ success: false, error: \"Error finding published chatbots from root \" + chatbot_id });\n    })\n\n  res.status(200).send(published_chatbots);\n\n})\n\nrouter.get('/:faq_kbid/jwt', roleChecker.hasRoleOrTypes('admin', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(req.query);\n\n  const chatbot_id = req.params.faq_kbid;\n  const id_project = req.projectid;\n\n  Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).select(\"+secret\").exec(function (err, faq_kb) {   //TODO add cache_bot_here\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n\n    var signOptions = {\n      issuer: 'https://tiledesk.com',\n      subject: 'bot',\n      audience: 'https://tiledesk.com/bots/' + faq_kb._id,\n      jwtid: uuidv4()\n    };\n\n    // TODO metti bot_? a user._id\n\n    // tolgo description, attributes\n    let botPayload = faq_kb.toObject();\n\n    let botSecret = botPayload.secret;\n    // winston.info(\"botSecret: \" + botSecret);\n\n    delete botPayload.secret;\n    delete botPayload.description;\n    delete botPayload.attributes;\n\n    var token = jwt.sign(botPayload, botSecret, signOptions);\n\n    res.json({ \"jwt\": token });\n  });\n});\n\n/**\n * This endpoint should be the only one reachble with role agent.\n * If the role is agent the response must contain only _id, name, or other non relevant info.\n */\nrouter.get('/', roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription']), function (req, res) {\n\n  winston.debug(\"req.query\", req.query);\n\n  const id_project = req.projectid;\n  winston.debug(\"GET FAQ-KB req projectid \" + id_project);\n\n\n  let restricted_mode = false;\n\n  let project_user = req.projectuser;\n  if (project_user && project_user.role === roleConstants.AGENT) {\n    restricted_mode = true;\n  }\n\n\n  /**\n   * if filter only for 'trashed = false', \n   * the bots created before the implementation of the 'trashed' property are not returned \n   */\n  let query = { id_project: id_project, trashed: { $in: [null, false] } };\n\n  if (restricted_mode === true) {\n    query.agents_available = {\n      $in: [null, true]\n    }\n  }\n\n  if (req.query.all != \"true\") {\n    query.type = { $ne: \"identity\" }\n  }\n\n  if (req.query.text) {\n    const search_obj = { $search: req.query.text };\n    if (req.query.language) search_obj.$language = req.query.language;\n    query.$text = search_obj;\n  }\n\n  const allowedFilters = [\"public\", \"certified\"];\n  allowedFilters.forEach(f => {\n    if (req.query[f] !== undefined) {\n      query[f] = req.query[f];\n    }\n  });\n\n  winston.debug(\"query\", query);\n\n  Faq_kb.find(query).lean().exec((err, faq_kbs) => {\n    if (err) {\n      winston.error('GET FAQ-KB ERROR ', err)\n      return res.status(500).send({ success: false, message: \"Unable to get chatbots\" });\n    }\n\n    if (restricted_mode === true) {\n      // Returns only: _id, name, id_project, language\n      faq_kbs = faq_kbs.map(({ webhook_enabled, intentsEngine, score, url, attributes, trained, certifiedTags, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)\n    }\n\n    res.json(faq_kbs)\n\n  })\n});\n\nrouter.post('/fork/:id_faq_kb', roleChecker.hasRole('admin'), async (req, res) => {\n\n  let id_faq_kb = req.params.id_faq_kb;\n  winston.debug('id_faq_kb: ' + id_faq_kb);\n\n  const api_url = process.env.API_URL || configGlobal.apiUrl;\n  winston.debug(\"fork --> base_url: \" + api_url); // check if correct\n\n  let current_project_id = req.projectid;\n  winston.debug(\"current project id: \" + current_project_id);\n\n  let landing_project_id = req.query.projectid;\n  winston.debug(\"landing project id \" + landing_project_id)\n\n  let public = req.query.public;\n  winston.debug(\"public \" + public);\n\n  let globals = req.query.globals;\n  winston.debug(\"export globals \" + globals);\n\n  let token = req.headers.authorization;\n\n  let cs = req.app.get('chatbot_service')\n  let chatbot;\n  try {\n    chatbot = await cs.getBotById(id_faq_kb, public, api_url, chatbot_templates_api_url, token, current_project_id, globals);\n  } catch (err) {\n    return res.status(500).send({ success: false, error: \"Unable to get chatbot to be forked\"});\n  }\n\n  if (!chatbot) {\n    return res.status(500).send({ success: false, message: \"Unable to get chatbot to be forked\" });\n  }\n\n  if (!globals) {\n    if (chatbot.attributes) {\n      delete chatbot.attributes.globals\n    }\n  }\n  \n  delete chatbot.modified;\n\n  chatbot.template = \"empty\";\n  \n  let savedChatbot\n  try {\n    savedChatbot = await cs.createBot(api_url, token, chatbot, landing_project_id);\n    winston.debug(\"savedChatbot: \", savedChatbot)\n  } catch(err) {\n    winston.error(\"Error creating new chatbot: \", err);\n    return res.status(500).send({ success: false, message: \"An error occurred during chatbot creation\"});\n  }\n\n  if (!savedChatbot) {\n    return res.status(500).send({ success: false, message: \"Unable to create new chatbot\" });\n  }\n  \n  let import_result\n  try {\n    import_result = await cs.importFaqs(api_url, savedChatbot._id, token, chatbot, landing_project_id);\n    winston.debug(\"imported: \", import_result);\n  } catch(err) {\n    winston.error(\"Error importing intents on new chatbot: \", err);\n    return res.status(500).send({ success: false, message: \"Error importing intents in the chatbot\"});\n  }\n\n  if (import_result?.success == false) {\n    return res.status(500).send({ success: false, message: \"Unable to import intents in the new chatbot\" });\n  }\n\n  return res.status(200).send({ message: \"Chatbot forked successfully\", bot_id: savedChatbot._id });\n\n})\n\nrouter.post('/importjson/:id_faq_kb', roleChecker.hasRole('admin'), upload.single('uploadFile'), async (req, res) => {\n\n  let chatbot_id = req.params.id_faq_kb;\n  let id_project = req.projectid;\n\n  winston.debug('import on id_faq_kb: ' + chatbot_id);\n\n  winston.debug('import with option create: ' + req.query.create);\n  winston.debug('import with option replace: ' + req.query.replace);\n  winston.debug('import with option overwrite: ' + req.query.overwrite);\n\n  let json_string;\n  let json;\n  if (req.file) {\n    json_string = req.file.buffer.toString('utf-8');\n    json = JSON.parse(json_string);\n  } else {\n    json = req.body;\n  }\n\n  winston.debug(\"json source \" + json_string)\n\n  // ****************************\n  // **** CREATE TRUE option ****\n  // ****************************\n  if (req.query.create === 'true') {\n    // if (json.subtype && (json.subtype === 'webhook' || json.subtype === 'copilot')) {\n    //   json.template = 'empty';\n    // }\n    json.template = 'empty';\n    let savedChatbot = await faqService.create(req.projectid, req.user.id, json)\n      .catch((err) => {\n        winston.error(\"Error creating new chatbot\")\n        return res.status(400).send({ succes: false, message: \"Error creatings new chatbot\", error: err })\n      })\n\n    // Edit attributes.rules\n    let attributes = json.attributes;\n    if (attributes &&\n      attributes.rules &&\n      attributes.rules.length > 0) {\n\n      await attributes.rules.forEach((rule) => {\n        if (rule.do &&\n          rule.do[0] &&\n          rule.do[0].message &&\n          rule.do[0].message.participants &&\n          rule.do[0].message.participants[0]) {\n          rule.do[0].message.participants[0] = \"bot_\" + savedChatbot._id\n          winston.debug(\"attributes rule new participant: \", rule.do[0].message.participants[0])\n        }\n      })\n    }\n\n    let chatbot_edited = { attributes: attributes };\n\n    let updatedChatbot = await Faq_kb.findByIdAndUpdate(savedChatbot._id, chatbot_edited, { new: true }).catch((err) => {\n      winston.error(\"Error updating chatbot attributes: \", err);\n      winston.debug(\"Skip Error updating chatbot attributes: \", err);\n      // return res.status(400).send({ success: false, message: \"Error updating chatbot attributes\", error: err })\n    })\n\n    botEvent.emit('faqbot.create', savedChatbot);\n\n    if (json.intents) {\n\n      json.intents.forEach(async (intent) => {\n\n        let new_faq = {\n          id_faq_kb: savedChatbot._id,\n          id_project: req.projectid,\n          createdBy: req.user.id,\n          intent_display_name: intent.intent_display_name,\n          intent_id: intent.intent_id,\n          question: intent.question,\n          answer: intent.answer,\n          reply: intent.reply,\n          form: intent.form,\n          enabled: intent.enabled,\n          webhook_enabled: intent.webhook_enabled,\n          language: intent.language,\n          actions: intent.actions,\n          attributes: intent.attributes\n        }\n\n        let faq = await Faq.create(new_faq).catch((err) => {\n          if (err.code == 11000) { // should never happen\n            winston.error(\"Duplicate intent_display_name.\");\n            winston.debug(\"Skip duplicated intent_display_name\"); // Don't stop the flow\n          } else {\n            winston.error(\"Error saving new faq: \", err)\n          }\n        })\n\n        if (faq) {\n          winston.debug(\"new intent created\")\n          faqBotEvent.emit('faq.create', faq);\n        }\n      })\n    }\n\n    if (updatedChatbot) {\n      return res.status(200).send(updatedChatbot)\n    } else {\n      return res.status(200).send(savedChatbot)\n    }\n  }\n  // *****************************\n  // **** CREATE FALSE option ****\n  // *****************************\n  else {\n\n    if (!chatbot_id) {\n      return res.status(400).send({ success: false, message: \"With replace or overwrite option a id_faq_kb must be provided\" })\n    }\n\n    let chatbot = await Faq_kb.findOne({ _id: chatbot_id, id_project: id_project }).catch((err) => {\n      winston.error(\"Error finding chatbot with id \" + chatbot_id);\n      return res.status(404).send({ success: false, message: \"Error finding chatbot with id \" + chatbot_id, error: err });\n    })\n\n    if (!chatbot) {\n      winston.error(\"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project);\n      return res.status(404).send({ success: false, message: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project });\n    }\n\n    if (json.webhook_enabled) {\n      chatbot.webhook_enabled = json.webhook_enabled;\n    }\n    if (json.webhook_url) {\n      chatbot.webhook_url = json.webhook_url;\n    }\n    if (json.language) {\n      chatbot.language = json.language;\n    }\n    if (json.name) {\n      chatbot.name = json.name;\n    }\n    if (json.description) {\n      chatbot.description = json.description;\n    }\n\n    if (json.attributes) {\n      let attributes = json.attributes;\n      if (attributes.rules &&\n        attributes.rules.length > 0) {\n        await attributes.rules.forEach((rule) => {\n          if (rule.do &&\n            rule.do[0] &&\n            rule.do[0].message &&\n            rule.do[0].message.participants &&\n            rule.do[0].message.participants[0]) {\n            rule.do[0].message.participants[0] = \"bot_\" + chatbot._id\n            winston.debug(\"attributes rule new participant: \" + rule.do[0].message.participants[0])\n          }\n        })\n        chatbot.attributes = json.attributes;\n      }\n    }\n\n    let updatedChatbot = await Faq_kb.findByIdAndUpdate(chatbot._id, chatbot, { new: true }).catch((err) => {\n      winston.error(\"Error updating chatbot\");\n      return res.status(400).send({ success: false, message: \"Error updating chatbot\", error: err })\n    })\n\n    botEvent.emit('faqbot.update', updatedChatbot);\n\n    // *****************************\n    // **** REPLACE TRUE option ****\n    // *****************************\n    if (req.query.replace === 'true') {\n      let result = await Faq.deleteMany({ id_faq_kb: chatbot._id }).catch((err) => {\n        winston.error(\"Unable to delete all existing faqs with id_faq_kb \" + chatbot._id);\n      })\n\n      winston.verbose(\"All faq for chatbot \" + chatbot._id + \" deleted successfully\")\n      winston.debug(\"DeleteMany faqs result \", result);\n    }\n\n    if (json.intents) {\n      await json.intents.forEach(async (intent) => {\n\n        let new_faq = {\n          id_faq_kb: updatedChatbot._id,\n          id_project: req.projectid,\n          createdBy: req.user.id,\n          intent_display_name: intent.intent_display_name,\n          intent_id: intent.intent_id,\n          question: intent.question,\n          answer: intent.answer,\n          reply: intent.reply,\n          form: intent.form,\n          enabled: intent.enabled,\n          webhook_enabled: intent.webhook_enabled,\n          language: intent.language,\n          actions: intent.actions,\n          attributes: intent.attributes\n        }\n\n        // *******************************\n        // **** OVERWRITE TRUE option ****\n        // *******************************\n        if (req.query.overwrite == \"true\") {\n\n          let savingResult = await Faq.findOneAndUpdate({ id_faq_kb: chatbot._id, intent_display_name: intent.intent_display_name }, new_faq, { new: true, upsert: true, rawResult: true }).catch((err) => {\n            winston.error(\"Unable to create faq: \", err);\n          })\n\n          if (savingResult) {\n            if (savingResult.lastErrorObject.updatedExisting === true) {\n              winston.verbose(\"updated existing intent\")\n              faqBotEvent.emit('faq.update', savingResult.value);\n            } else {\n              winston.verbose(\"new intent created\")\n              faqBotEvent.emit('faq.create', savingResult.value);\n            }\n          }\n\n          // ********************************\n          // **** OVERWRITE FALSE option ****\n          // ********************************\n        } else {\n\n          let faq = await Faq.create(new_faq).catch((err) => {\n            if (err.code == 11000) {\n              winston.error(\"Duplicate intent_display_name.\");\n              winston.verbose(\"Skip duplicated intent_display_name\");\n            } else {\n              winston.error(\"Error creating new intent: \", err);\n            }\n          })\n\n          if (faq) {\n            winston.debug(\"new intent created: \", faq)\n            faqBotEvent.emit('faq.create', faq);\n          }\n        }\n      })\n    }\n\n    if (updatedChatbot) {\n      return res.send(updatedChatbot);\n    } else {\n      return res.send(chatbot);\n    }\n  }\n})\n\nrouter.get('/exportjson/:id_faq_kb', roleChecker.hasRole('admin'), (req, res) => {\n\n  winston.debug(\"exporting bot...\")\n\n  const chatbot_id = req.params.id_faq_kb;\n  const id_project = req.projectid;\n\n  Faq_kb.findOne({ _id: chatbot_id }, (err, faq_kb) => {\n    if (err) {\n      winston.error('GET FAQ-KB ERROR ', err)\n      return res.status(500).send({ success: false, msg: 'Error getting bot.' });\n    }\n    \n    if (!faq_kb) {\n      return res.status(404).send({ success: false, msg: \"Chatbot not found with id \" + chatbot_id });\n    }\n\n    const isPublic = faq_kb.public === true;\n    const isOwner = faq_kb.id_project === id_project;\n\n    if (!isPublic && !isOwner) {\n      return res.status(403).send({\n        success: false,\n        msg: \"Chatbot not found with id \" + chatbot_id + \" for project \" + id_project\n      });\n    }\n\n    winston.debug('FAQ-KB: ', faq_kb)\n\n    faqService.getAll(chatbot_id).then((faqs) => {\n\n      // delete from exclude map intent_id\n      const intents = faqs.map(({ _id, id_project, topic, status, chatbot_id, createdBy, createdAt, updatedAt, __v, ...keepAttrs }) => keepAttrs)\n\n      if (!req.query.globals) {\n        winston.verbose(\"Delete globals from attributes!\")\n        if (faq_kb.attributes) {\n          delete faq_kb.attributes.globals;\n        }\n      }\n\n      let json = {\n        webhook_enabled: faq_kb.webhook_enabled,\n        webhook_url: faq_kb.webhook_url,\n        language: faq_kb.language,\n        name: faq_kb.name,\n        slug: faq_kb.slug,\n        type: faq_kb.type,\n        subtype: faq_kb.subtype,\n        description: faq_kb.description,\n        attributes: faq_kb.attributes,\n        intents: intents\n      }\n\n      if (req.query.intentsOnly == 'true') {\n        let intents_obj = {\n          intents: intents\n        }\n        let intents_string = JSON.stringify(intents_obj);\n        res.set({ \"Content-Disposition\": \"attachment; filename=\\\"intents.json\\\"\" });\n        return res.send(intents_string);\n\n      } else {\n\n        // if (req.query.file == \"false\") {\n        //   return res.status(200).send(json);\n        // }\n        let json_string = JSON.stringify(json);\n        res.set({ \"Content-Disposition\": \"attachment; filename=\\\"bot.json\\\"\" });\n        return res.send(json_string);\n      }\n\n    }).catch((err) => {\n      winston.error('GET FAQ ERROR: ', err)\n      return res.status(500).send({ success: false, msg: 'Error getting faqs.' });\n    })\n  })\n})\n\nrouter.post('/:faq_kbid/training', roleChecker.hasRole('admin'), function (req, res) {\n\n  winston.debug(req.body);\n  winston.info(req.params.faq_kbid + \"/training called\");\n\n  var update = {};\n  update.trained = true;\n  // update._id = req.params.faq_kbid;\n\n  winston.debug(\"update\", update);\n  // \"$set\": req.params.faq_kbid\n\n  Faq_kb.findByIdAndUpdate(req.params.faq_kbid, update, { new: true, upsert: true }, function (err, updatedFaq_kb) {   //TODO add cache_bot_here\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    botEvent.emit('faqbot.update', updatedFaq_kb);\n    res.json(updatedFaq_kb);\n  });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/faqpub.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Faq = require(\"../models/faq\");\nvar winston = require('../config/winston');\n\n\n\n\nrouter.get('/', function (req, res, next) {\n  var query = {};\n\n  winston.debug(\"GET ALL FAQ OF THE BOT ID (req.query): \", req.query);\n\n  if (req.query.id_faq_kb) {\n    query.id_faq_kb = req.query.id_faq_kb;\n  }\n\n  if (req.query.text) {\n    winston.debug(\"GET FAQ req.projectid\", req.projectid);\n\n    // query.$text = req.query.text;\n    query.$text = { \"$search\": req.query.text };\n    query.id_project = req.projectid\n  }\n\n  winston.debug(\"GET FAQ query\", query);\n\n  // query.$text = {\"$search\": \"question\"};\n\n  // TODO ORDER BY SCORE\n  // return Faq.find(query,  {score: { $meta: \"textScore\" } }) \n  // .sort( { score: { $meta: \"textScore\" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n\n  \n  return Faq.find(query).\n    populate({path:'faq_kb'}).//, match: { trashed: { $in: [null, false] } }}).\n    exec(function (err, faq) {\n      winston.debug(\"GET FAQ \", faq);\n\n      if (err) {\n        winston.error('GET FAQ err ', err)\n        return next(err)\n      };\n      winston.debug('GET FAQ  ', faq)\n      res.json(faq);\n\n    });\n\n  // Faq.find(query, function (err, faq) {\n  //   if (err) {\n  //     winston.debug('GET FAQ err ', err)\n  //     return next(err)\n  //   };\n  //   winston.debug('GET FAQ  ', faq)\n  //   res.json(faq);\n  // });\n\n});\n\n\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/files.js",
    "content": "var express = require('express');\nconst multer  = require('multer');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar winston = require('../config/winston');\nvar pathlib = require('path');\n\n\nvar router = express.Router();\n\n\n\nconst FileGridFsService = require('../services/fileGridFsService.js');\nconst { path } = require('../models/tag');\n\nconst fileService = new FileGridFsService(\"files\");\nconst fallbackFileService = new FileGridFsService(\"images\");\n\n\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;\n  winston.info(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.info(\"Max upload file size is infinity\");\n}\nconst upload = multer({ storage: fileService.getStorage(\"files\"),limits: uploadlimits});\n\n/*\ncurl -u andrea.leo@f21.it:123456 \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/README.md\" \\\n  http://localhost:3000/files/users/\n\n  */\n\n// DEPRECATED FROM VERSION 2.14.24\n// router.post('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], upload.single('file'), (req, res, next) => {\n\n//   winston.verbose(\"files/users\")\n//   return res.status(201).json({\n//     message: 'File uploded successfully',\n//     filename: req.file.filename\n//   });\n\n// });\n\n/*\ncurl \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/README.md\" \\\n  http://localhost:3000/files/public/\n\n\n\n  */\n\n// DEPRECATED FROM VERSION 2.14.24\n// router.post('/public', upload.single('file'), (req, res, next) => {\n//   winston.debug(\"files/public\")\n//       return res.status(201).json({\n//           message: 'File uploded successfully',\n//           filename: req.file.filename\n//       });    \n// });\n\n\n\n\nrouter.get(\"/\", async (req, res) => {\n  winston.debug('path', req.query.path);\n  \n  let fService = fileService;\n  try {\n    let file = await fileService.find(req.query.path);\n    res.set({ \"Content-Length\": file.length});\n    res.set({ \"Content-Type\": file.contentType});\n  } catch (e) {\n    if (e.code == \"ENOENT\") {\n      winston.debug(`File ${req.query.path} not found on primary file service. Fallback to secondary.`)\n      \n      try {\n        let file = await fallbackFileService.find(req.query.path)\n        res.set({ \"Content-Length\": file.length });\n        res.set({ \"Content-Type\": file.contentType });\n        fService = fallbackFileService;\n      } catch (e) {\n        if (e.code == \"ENOENT\") {\n          winston.debug(`File ${req.query.path} not found on secondary file service.`)\n          return res.status(404).send({ success: false, error: 'File not found.' });\n        } else {\n          winston.error('Error getting file: ', e);\n          return res.status(500).send({success: false, error: 'Error getting file.'});\n        }\n      }\n    } else {\n      winston.error('Error getting file', e);\n      return res.status(500).send({success: false, error: 'Error getting file.'});\n    }\n  }\n  \n  fService.getFileDataAsStream(req.query.path).on('error', (e)=> {\n    if (e.code == \"ENOENT\") {\n      winston.debug('File not found: '+req.query.path);\n      return res.status(404).send({success: false, error: 'File not found.'});\n    } else {\n      winston.error('Error getting the file', e);\n      return res.status(500).send({success: false, error: 'Error getting file.'});\n    }      \n  }).pipe(res);\n});\n\n\nrouter.get(\"/download\", async (req, res) => {\n  winston.debug('path', req.query.path);\n  let filename = pathlib.basename(req.query.path);\n  winston.debug(\"filename:\"+filename);\n\n  let fService = fileService;\n  try {\n    await fileService.find(req.query.path);\n  } catch (e) {\n    if (e.code == \"ENOENT\") {\n      winston.debug(`File ${req.query.path} not found on primary file service. Fallback to secondary.`)\n      try {\n        await fallbackFileService.find(req.query.path);\n        fService = fallbackFileService;\n      } catch (e) {\n        if (e.code == \"ENOENT\") {\n          winston.debug(`File ${req.query.path} not found on secondary file service.`)\n          return res.status(404).send({ success: false, error: 'File not found.' });\n        } else {\n          winston.error('Error getting file: ', e);\n          return res.status(500).send({success: false, error: 'Error getting file.'});\n        }\n      }\n    } else {\n      winston.error('Error getting file', e);\n      return res.status(500).send({success: false, error: 'Error getting file.'});\n    }\n  }\n\n  res.attachment(filename);\n  fService.getFileDataAsStream(req.query.path).on('error', (e)=> {\n    if (e.code == \"ENOENT\") {\n      winston.debug('File not found: '+req.query.path);\n      return res.status(404).send({success: false, error: 'File not found.'});\n    } else {\n      winston.error('Error getting the file', e);\n      return res.status(500).send({success: false, error: 'Error getting file.'});\n    }      \n  }).pipe(res);\n});\n\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/filesp.js",
    "content": "const express = require('express');\nconst router = express.Router();\nconst pathlib = require('path');\nconst mongoose = require('mongoose');\nconst multer  = require('multer');\nconst passport = require('passport');\nconst mime = require('mime-types');\nconst path = require('path');\nconst sharp = require('sharp');\nconst verifyFileContent = require('../middleware/file-type.js');\n\nrequire('../middleware/passport.js')(passport);\nconst validtoken = require('../middleware/valid-token.js')\nconst roleChecker = require('../middleware/has-role.js');\nconst winston = require('../config/winston.js');\nconst FileGridFsService = require('../services/fileGridFsService.js');\nconst roleConstants = require('../models/roleConstants.js');\nconst faq_kb = require('../models/faq_kb');\nconst project_user = require('../models/project_user');\n\nconst fileService = new FileGridFsService(\"files\");\nconst fallbackFileService = new FileGridFsService(\"images\");;\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE || 1024000; // 1MB\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = { fileSize: parseInt(MAX_UPLOAD_FILE_SIZE) } ;\n  winston.info(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.info(\"Max upload file size is infinity\");\n}\n\n/**\n * Default: '2592000' (30 days)\n * Examples:\n * - '30' (30 seconds)\n */\nconst chatFileExpirationTime = parseInt(process.env.CHAT_FILE_EXPIRATION_TIME || '2592000', 10);\n\n/**\n * Default: \".jpg,.jpeg,.png,.gif,.pdf,.txt\"\n * Examples: \n * - '* /*' without spaces (all extension)\n * Deprecated: \"application/pdf,image/png,...\"\n */\nconst default_chat_allowed_extensions = process.env.CHAT_FILES_ALLOW_LIST || \".jpg,.jpeg,.png,.gif,.pdf,.txt\"; \nconst default_assets_allowed_extensions = process.env.ASSETS_FILES_ALLOW_LIST || \".jpg,.jpeg,.png,.gif,.pdf,.txt,.csv,.doc,.docx\"; //,.xls,.xlsx,.ppt,.pptx,.zip,.rar\nconst images_extensions = [ \".png\", \".jpg\", \".jpeg\", \".gif\" ];\n\nconst fileFilter = (extensionsSource = 'chat') => {\n  return (req, file, cb) => {\n\n    const project = req.project;\n    const pu = req.projectuser;\n\n    let allowed_extensions;\n    let allowed_mime_types;\n\n    if (extensionsSource === 'avatar') {\n      // Avatar only accepts image extensions\n      allowed_extensions = images_extensions.join(',');\n    } else if (extensionsSource === 'assets') {\n      allowed_extensions = default_assets_allowed_extensions;\n    } else if (pu.roleType === 2 || pu.role === roleConstants.GUEST) {\n      allowed_extensions = project?.widget?.allowedUploadExtentions || default_chat_allowed_extensions;\n    } else {\n      allowed_extensions = project?.settings?.allowed_upload_extentions || default_chat_allowed_extensions;\n    }\n\n    if (allowed_extensions !== \"*/*\") {\n      allowed_mime_types = getMimeTypes(allowed_extensions);\n      if (!file.originalname) {\n        return cb(new Error(\"File original name is required\"));\n      }\n      const ext = path.extname(file.originalname).toLowerCase();\n\n      if (!allowed_extensions.includes(ext)) {\n        const error = new Error(`File extension ${ext} is not allowed${extensionsSource === 'avatar' ? ' for avatar' : ''}`);\n        error.status = 403;\n        return cb(error);\n      }\n\n      const expectedMimeType = mime.lookup(ext);\n      if (expectedMimeType && !areMimeTypesEquivalent(file.mimetype, expectedMimeType)) {\n        const error = new Error(`File content does not match mimetype. Detected: ${file.mimetype}, provided: ${expectedMimeType}`);\n        error.status = 403;\n        return cb(error);\n      }\n\n      return cb(null, true);\n    } else {\n      return cb(null, true);\n    }\n  }\n}\n\nfunction getMimeTypes(allowed_extension) {\n  const extension = allowed_extension.split(',').map(e => e.trim().toLowerCase());\n  const allowedMimeTypes = extension.map(ext => mime.lookup(ext)).filter(Boolean);\n  return allowedMimeTypes;\n}\n\n/**\n * Checks if two MIME types are equivalent, accepting common aliases\n * Examples:\n * - audio/wav === audio/wave\n * - audio/x-wav === audio/wave\n * - image/jpeg === image/jpg\n */\nfunction areMimeTypesEquivalent(mimeType1, mimeType2) {\n  if (!mimeType1 || !mimeType2) return false;\n  if (mimeType1 === mimeType2) return true;\n  \n  // Normalize to lowercase for comparison\n  const m1 = mimeType1.toLowerCase();\n  const m2 = mimeType2.toLowerCase();\n  if (m1 === m2) return true;\n  \n  // Common MIME type aliases\n  const aliases = {\n    'audio/wav': ['audio/wave', 'audio/x-wav', 'audio/vnd.wave'],\n    'audio/wave': ['audio/wav', 'audio/x-wav', 'audio/vnd.wave'],\n    'audio/x-wav': ['audio/wav', 'audio/wave', 'audio/vnd.wave'],\n    'audio/vnd.wave': ['audio/wav', 'audio/wave', 'audio/x-wav'],\n    'audio/mpeg': ['audio/opus', 'audio/mp3', 'audio/webm'],\n    'audio/mp3': ['audio/mpeg', 'audio/opus', 'audio/webm'],\n    'audio/opus': ['audio/mpeg', 'audio/mp3', 'audio/webm'],\n    'audio/webm': ['audio/mpeg', 'audio/mp3', 'audio/opus'],\n    'image/jpeg': ['image/jpg'],\n    'image/jpg': ['image/jpeg'],\n    'application/x-zip-compressed': ['application/zip'],\n    'application/zip': ['application/x-zip-compressed'],\n  };\n  \n  // Check if m1 is an alias of m2 or vice versa\n  if (aliases[m1] && aliases[m1].includes(m2)) return true;\n  if (aliases[m2] && aliases[m2].includes(m1)) return true;\n  \n  return false;\n}\n\nconst uploadChat = multer({\n  storage: fileService.getStorage(\"files\"),\n  fileFilter: fileFilter('chat'),\n  limits: uploadlimits\n}).single('file');\n\nconst uploadAssets = multer({\n  storage: fileService.getStorageProjectAssets(\"files\"),\n  fileFilter: fileFilter('assets'),\n  limits: uploadlimits\n}).single('file');\n\nconst uploadAvatar = multer({\n  storage: fileService.getStorageAvatarFiles(\"files\"),\n  fileFilter: fileFilter('avatar'),\n  limits: uploadlimits\n}).single('file');\n\n\n// *********************** //\n// ****** Endpoints ****** //\n// *********************** //\n\nrouter.post('/chat', [\n  passport.authenticate(['basic', 'jwt'], { session: false }),\n  validtoken,\n  roleChecker.hasRoleOrTypes('guest', ['bot','subscription'])\n], async (req, res) => {\n\n  const expireAt = new Date(Date.now() + chatFileExpirationTime * 1000);\n  req.expireAt = expireAt;\n  uploadChat(req, res, async (err) => {\n    if (err instanceof multer.MulterError) {\n      // A Multer error occurred when uploading\n      winston.error(`Multer replied with code ${err?.code} and message \"${err?.message}\"`);\n      let status = 400;\n      if (err?.code === 'LIMIT_FILE_SIZE') {\n        status = 413;\n      }\n      return res.status(status).send({ success: false, error: err?.message || 'An error occurred while uploading the file', code: err.code });\n    } else if (err) {\n      // An unknown error occurred when uploading.\n      winston.error(`Multer replied with status ${err?.status} and message \"${err?.message}\"`);\n      let status = err?.status || 400;\n      return res.status(status).send({ success: false, error: err.message || \"An error occurred while uploading the file\" })\n    }\n    try {\n      const buffer = await fileService.getFileDataAsBuffer(req.file.filename);\n      await verifyFileContent(buffer, req.file.mimetype);\n      await mongoose.connection.db.collection('files.chunks').updateMany(\n        { files_id: req.file.id },\n        { $set: { \"metadata.expireAt\": req.file.metadata.expireAt }}\n      )\n      return res.status(201).send({ message: \"File uploaded successfully\", filename: req.file.filename })\n    } catch (err) {\n      if (err?.source === \"FileContentVerification\") {\n        let error_message = err?.message || \"Content verification failed\";\n        winston.warn(\"File content verification failed. Message: \", error_message);\n        return res.status(403).send({ success: false, error: error_message })\n      }\n      winston.error(\"Error saving file: \", err);\n      return res.status(500).send({ success: false, error: \"Error updating file chunks\" });\n    }\n  })\n\n})\n\n\nrouter.post('/assets', [\n  passport.authenticate(['basic', 'jwt'], { session: false }),\n  validtoken,\n  roleChecker.hasRoleOrTypes('admin', ['bot','subscription'])\n], async (req, res) => {\n  // Assets have no retention by default, but can be set via query parameter\n  let customExpiration = parseInt(req.query?.expiration, 10);\n  if (customExpiration && !isNaN(customExpiration) && customExpiration > 0) {\n    req.expireAt = new Date(Date.now() + customExpiration * 1000);\n  }\n\n\n  uploadAssets(req, res, async (err) => {\n    if (err instanceof multer.MulterError) {\n      // A Multer error occurred when uploading\n      winston.error(`Multer replied with code ${err?.code} and message \"${err?.message}\"`);\n      let status = 400;\n      if (err?.code === 'LIMIT_FILE_SIZE') {\n        status = 413;\n      }\n      return res.status(status).send({ success: false, error: err?.message || 'An error occurred while uploading the file', code: err.code });\n    } else if (err) {\n      // An unknown error occurred when uploading.\n      winston.error(`Multer replied with status ${err?.status} and message \"${err?.message}\"`);\n      let status = err?.status || 400;\n      return res.status(status).send({ success: false, error: err.message || \"An error occurred while uploading the file\" })\n    }\n\n    try {\n\n      const buffer = await fileService.getFileDataAsBuffer(req.file.filename);\n      await verifyFileContent(buffer, req.file.mimetype);\n\n      if (req.file.metadata && req.file.metadata.expireAt) {\n        await mongoose.connection.db.collection('files.chunks').updateMany(\n          { files_id: req.file.id },\n          { $set: { \"metadata.expireAt\": req.file.metadata.expireAt }}\n        );\n      }\n\n      const ext = path.extname(req.file.originalname).toLowerCase();\n      let thumbnail;\n\n      // Generate thumbnail for images\n      if (images_extensions.includes(ext)) {\n        const buffer = await fileService.getFileDataAsBuffer(req.file.filename);\n        const thumbFilename = req.file.filename.replace(/([^/]+)$/, \"thumbnails_200_200-$1\");\n        const resized = await sharp(buffer).resize(200, 200).toBuffer();\n        \n        const thumbMetadata = req.expireAt ? { metadata: { expireAt: req.expireAt } } : undefined;\n        // Use the same contentType as the original file for the thumbnail\n        await fileService.createFile(thumbFilename, resized, undefined, req.file.mimetype, thumbMetadata);\n        \n        if (req.expireAt) {\n          await mongoose.connection.db.collection('files.chunks').updateMany(\n            { files_id: ( await fileService.find(thumbFilename))._id },\n            { $set: { \"metadata.expireAt\": req.expireAt }}\n          );\n        }\n        thumbnail = thumbFilename;\n      }\n\n      return res.status(201).send({\n        message: 'File uploaded successfully',\n        filename: encodeURIComponent(req.file.filename),\n        thumbnail: thumbnail ? encodeURIComponent(thumbnail) : undefined\n      })\n\n    } catch (err) {\n      if (err?.source === \"FileContentVerification\") {\n        let error_message = err?.message || \"Content verification failed\";\n        winston.warn(\"File content verification failed. Message: \", error_message);\n        return res.status(403).send({ success: false, error: error_message })\n      }\n\n      winston.error(\"Error uploading asset\", err);\n      return res.status(500).send({ success: false, error: \"Error uploading asset\" });\n\n    }\n  })\n})\n\n/**\n * Upload user profile photo or bot avatar\n * Path: uploads/users/{user_id|bot_id}/images/photo.jpg\n * This maintains compatibility with clients that expect fixed paths.\n * Profile photos/avatars have no retention.\n */\nrouter.post('/users/photo', [\n  passport.authenticate(['basic', 'jwt'], { session: false }),\n  validtoken,\n  roleChecker.hasRoleOrTypes('agent', ['bot','subscription'])\n], async (req, res) => {\n\n  uploadAvatar(req, res, async (err) => {\n    if (err instanceof multer.MulterError) {\n      // A Multer error occurred when uploading\n      winston.error(`Multer replied with code ${err?.code} and message \"${err?.message}\"`);\n      let status = 400;\n      if (err?.code === 'LIMIT_FILE_SIZE') {\n        status = 413;\n      }\n      return res.status(status).send({ success: false, error: err?.message || 'An error occurred while uploading the file', code: err.code });\n    } else if (err) {\n      // An unknown error occurred when uploading.\n      winston.error(`Multer replied with status ${err?.status} and message \"${err?.message}\"`);\n      let status = err?.status || 400;\n      return res.status(status).send({ success: false, error: err.message || \"An error occurred while uploading the file\" })\n    }\n\n    try {\n      winston.debug(\"/users/photo\");\n  \n      if (!req.file) {\n        return res.status(400).send({ success: false, error: 'No file uploaded' });\n      }\n  \n      let userid = req.user.id;\n      let bot_id;\n      let entity_id = userid;\n  \n      if (req.query.bot_id) {\n        bot_id = req.query.bot_id;\n  \n        let chatbot = await faq_kb.findById(bot_id).catch((err) => {\n          winston.error(\"Error finding bot \", err);\n          return res.status(500).send({ success: false, error: \"Unable to find chatbot with id \" + bot_id });\n        });\n  \n        if (!chatbot) {\n          return res.status(404).send({ success: false, error: \"Chatbot not found\" });\n        }\n  \n        let id_project = chatbot.id_project;\n  \n        let puser = await project_user.findOne({ id_user: userid, id_project: id_project }).catch((err) => {\n          winston.error(\"Error finding project user: \", err);\n          return res.status(500).send({ success: false, error: \"Unable to find project user for user \" + userid + \"in project \" + id_project });\n        });\n  \n        if (!puser) {\n          winston.warn(\"User \" + userid + \" doesn't belong to the project \" + id_project);\n          return res.status(401).send({ success: false, error: \"You don't belong to the chatbot's project\" });\n        }\n  \n        if ((puser.role !== roleConstants.ADMIN) && (puser.role !== roleConstants.OWNER)) {\n          winston.warn(\"User with role \" + puser.role + \" can't modify the chatbot\");\n          return res.status(403).send({ success: false, error: \"You don't have the role required to modify the chatbot\" });\n        }\n  \n        entity_id = bot_id;\n      }\n  \n      var destinationFolder = 'uploads/users/' + entity_id + \"/images/\";\n      winston.debug(\"destinationFolder:\" + destinationFolder);\n  \n      var thumFilename = destinationFolder + 'thumbnails_200_200-photo.jpg';\n  \n      winston.debug(\"req.file.filename:\" + req.file.filename);\n      const buffer = await fileService.getFileDataAsBuffer(req.file.filename);\n  \n      try {\n        const resizeImage = await sharp(buffer).resize(200, 200).toBuffer();\n        // Use the same contentType as the original file for the thumbnail\n        await fileService.createFile(thumFilename, resizeImage, undefined, req.file.mimetype);\n        let thumFile = await fileService.find(thumFilename);\n        winston.debug(\"thumFile\", thumFile);\n  \n        return res.status(201).json({\n          message: 'Image uploaded successfully',\n          filename: encodeURIComponent(req.file.filename),\n          thumbnail: encodeURIComponent(thumFilename)\n        });\n      } catch (thumbErr) {\n        winston.error(\"Error generating or creating thumbnail\", thumbErr);\n        // Still return success for the main file, but log thumbnail error\n        return res.status(201).json({\n          message: 'Image uploaded successfully',\n          filename: encodeURIComponent(req.file.filename),\n          thumbnail: undefined\n        });\n      }\n  \n    } catch (error) {\n      winston.error('Error uploading user image.', error);\n      return res.status(500).send({ success: false, error: 'Error uploading user image.' });\n    }\n\n  })\n})\n\n\nrouter.get(\"/\", [\n  passport.authenticate(['basic', 'jwt'], { session: false }), \n  validtoken,\n], async (req, res) => {\n  winston.debug('path', req.query.path);\n\n  if (req.query.as_attachment) {\n    res.set({ \"Content-Disposition\": \"attachment; filename=\\\"\"+req.query.path+\"\\\"\" });\n  }\n  \n  let fService = fileService;\n  try {\n    let file = await fileService.find(req.query.path);\n    res.set({ \"Content-Length\": file.length});\n    res.set({ \"Content-Type\": file.contentType});\n  } catch (e) {\n    if (e.code == \"ENOENT\") {\n      winston.debug(`File ${req.query.path} not found on primary file service. Fallback to secondary.`)\n      \n      // To instantiate fallbackFileService here where needed you need to wait for the open event.\n      // Instance moved on top\n      // await new Promise(r => fallbackFileService.conn.once(\"open\", r));\n\n      try {\n        let file = await fallbackFileService.find(req.query.path)\n        res.set({ \"Content-Length\": file.length });\n        res.set({ \"Content-Type\": file.contentType });\n        fService = fallbackFileService;\n      } catch (e) {\n        if (e.code == \"ENOENT\") {\n          winston.debug(`File ${req.query.path} not found on seconday file service.`)\n          return res.status(404).send({ success: false, error: 'File not found.' });\n        }else {\n          winston.error('Error getting file: ', e);\n          return res.status(500).send({success: false, error: 'Error getting file.'});\n        }\n      }\n\n    } else {\n      winston.error('Error getting file', e);\n      return res.status(500).send({success: false, error: 'Error getting file.'});\n    }\n  }\n  \n  fService.getFileDataAsStream(req.query.path).pipe(res);\n});\n\nrouter.get(\"/download\", [\n  passport.authenticate(['basic', 'jwt'], { session: false }), \n  validtoken,\n], (req, res) => {\n  winston.debug('path', req.query.path);\n\n  let filename = pathlib.basename(req.query.path);\n  winston.debug(\"filename:\"+filename);\n\n  res.attachment(filename);\n  fileService.getFileDataAsStream(req.query.path).pipe(res);\n});\n\n/**\n * Delete a file (and its thumbnail if it's an image)\n * Works for both profile photos/avatars and project assets\n * \n * Example:\n * curl -v -X DELETE -u user:pass \\\n *   http://localhost:3000/filesp?path=uploads%2Fusers%2F65c5f3599faf2d04cd7da528%2Fimages%2Fphoto.jpg\n * \n * curl -v -X DELETE -u user:pass \\\n *   http://localhost:3000/filesp?path=uploads%2Fprojects%2F65c5f3599faf2d04cd7da528%2Ffiles%2Fuuid%2Flogo.png\n */\nrouter.delete(\"/\", [\n  passport.authenticate(['basic', 'jwt'], { session: false }), \n  validtoken,\n], async (req, res) => {\n  try {\n    winston.debug(\"delete file\");\n    \n    let filePath = req.query.path;\n    if (!filePath) {\n      return res.status(400).send({ success: false, error: 'Path parameter is required' });\n    }\n    \n    winston.debug(\"path:\" + filePath);\n\n    let filename = pathlib.basename(filePath);\n    winston.debug(\"filename:\" + filename);\n\n    if (!filename) {\n      winston.warn('Error deleting file. No filename specified:' + filePath);\n      return res.status(400).send({ success: false, error: 'No filename specified in path' });\n    }\n\n    // Determine which service to use based on path\n    // Try primary service first (files bucket)\n    let fService = fileService;\n    let fileExists = false;\n    \n    try {\n      await fileService.find(filePath);\n      fileExists = true;\n    } catch (e) {\n      if (e.code == \"ENOENT\") {\n        winston.debug(`File ${filePath} not found on primary file service. Trying fallback.`);\n        try {\n          await fallbackFileService.find(filePath);\n          fService = fallbackFileService;\n          fileExists = true;\n        } catch (e2) {\n          if (e2.code == \"ENOENT\") {\n            winston.debug(`File ${filePath} not found on fallback file service.`);\n            return res.status(404).send({ success: false, error: 'File not found.' });\n          } else {\n            winston.error('Error checking file on fallback service: ', e2);\n            return res.status(500).send({ success: false, error: 'Error checking file existence.' });\n          }\n        }\n      } else {\n        winston.error('Error checking file on primary service: ', e);\n        return res.status(500).send({ success: false, error: 'Error checking file existence.' });\n      }\n    }\n\n    // Delete the main file\n    try {\n      const deletedFile = await fService.deleteFile(filePath);\n      winston.debug(\"File deleted successfully:\", deletedFile.filename);\n\n      // Check if this is an image and try to delete thumbnail\n      // Thumbnail pattern: thumbnails_200_200-{filename}\n      // For profile photos: thumbnails_200_200-photo.jpg\n      // For assets: thumbnails_200_200-{original_filename}\n      const isImage = images_extensions.some(ext => filename.toLowerCase().endsWith(ext));\n      \n      if (isImage) {\n        let thumbFilename = 'thumbnails_200_200-' + filename;\n        let thumbPath = filePath.replace(filename, thumbFilename);\n        winston.debug(\"thumbPath:\" + thumbPath);\n\n        try {\n          // Try to delete thumbnail from the same service\n          await fService.deleteFile(thumbPath);\n          winston.debug(\"Thumbnail deleted successfully:\" + thumbPath);\n        } catch (thumbErr) {\n          // Thumbnail might not exist or be in different service, try fallback\n          if (thumbErr.code == \"ENOENT\" || thumbErr.msg == \"File not found\") {\n            winston.debug(`Thumbnail ${thumbPath} not found on ${fService === fileService ? 'primary' : 'fallback'} service. Trying other service.`);\n            \n            const otherService = fService === fileService ? fallbackFileService : fileService;\n            try {\n              await otherService.deleteFile(thumbPath);\n              winston.debug(\"Thumbnail deleted from fallback service:\" + thumbPath);\n            } catch (fallbackThumbErr) {\n              // Thumbnail doesn't exist, that's ok\n              winston.debug(`Thumbnail ${thumbPath} not found on fallback service either. Skipping.`);\n            }\n          } else {\n            winston.error('Error deleting thumbnail:', thumbErr);\n            // Don't fail the whole request if thumbnail deletion fails\n          }\n        }\n      }\n\n      return res.status(200).json({\n        message: 'File deleted successfully',\n        filename: encodeURIComponent(deletedFile.filename)\n      });\n\n    } catch (deleteErr) {\n      winston.error('Error deleting file:', deleteErr);\n      return res.status(500).send({ success: false, error: 'Error deleting file.' });\n    }\n\n  } catch (error) {\n    winston.error('Error in delete endpoint:', error);\n    return res.status(500).send({ success: false, error: 'Error deleting file.' });\n  }\n});\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/group.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Group = require(\"../models/group\");\nvar groupEvent = require(\"../event/groupEvent\");\nvar winston = require('../config/winston');\nconst departmentService = require('../services/departmentService');\n\n\n\nrouter.post('/', function (req, res) {\n\n  winston.debug('SAVE GROUP ', req.body);\n  var newGroup = new Group({\n    name: req.body.name,\n    members: req.body.members,\n    trashed: false,\n    id_project: req.projectid,\n    createdBy: req.user.id,\n    updatedBy: req.user.id\n  });\n\n  newGroup.save(function (err, savedGroup) {\n    if (err) {\n      winston.error('Error creating the group ', err);\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n\n  \n    groupEvent.emit('group.create', savedGroup);\n    res.json(savedGroup);\n  });\n});\n\nrouter.put('/:groupid', function (req, res) {\n\n  winston.debug(req.body);\n\n  var update = {};\n  if (req.body.name!=undefined) {\n    update.name = req.body.name;\n  }\n  if (req.body.members!=undefined) {\n    update.members = req.body.members;\n  }\n  if (req.body.trashed!=undefined) {\n    update.trashed = req.body.trashed;\n  }\n  if (req.body.attributes!=undefined) {\n    update.attributes = req.body.attributes;\n  }\n  \n\n  Group.findByIdAndUpdate(req.params.groupid, update, { new: true, upsert: true }, function (err, updatedGroup) {\n    if (err) {\n      winston.error('Error putting the group ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    groupEvent.emit('group.update', updatedGroup);\n    res.json(updatedGroup);\n  });\n});\n\nrouter.put('/enable/:groupid', async (req, res) => {\n\n  let group_id = req.params.groupid;\n\n  Group.findByIdAndUpdate(group_id, { enabled: true }, { new: true, upsert: true }, (err, updatedGroup) => {\n    if (err) {\n      winston.error(\"Error enabling the group: \", err);\n      return res.status(500).send({ success: false, error: \"Error enabling group\" })\n    }\n\n    groupEvent.emit('group.update', updatedGroup);\n    res.status(200).send(updatedGroup);\n  })\n\n})\n\nrouter.put('/disable/:groupid', async (req, res) => {\n\n  let id_project = req.projectid;\n  let group_id = req.params.groupid;\n\n  const isInDepartment = await departmentService.isGroupInProjectDepartment(id_project, group_id).catch((err) => {\n    winston.error(\"Error checking if group belongs to the department: \", err);\n    return res.status(500).send({ success: false, error: \"Unable to verify group-department association due to an error\" })\n  })\n\n  if (isInDepartment) {\n    winston.verbose(\"The group \" + group_id + \" belongs to a department and cannot be disabled\");\n    return res.status(403).send({ success: false, error: \"Unable to disabled a group associated with a department\" })\n  }\n\n  Group.findByIdAndUpdate(group_id, { enabled: false }, { new: true, upsert: true }, (err, updatedGroup) => {\n    if (err) {\n      winston.error(\"Error disabling the group: \", err);\n      return res.status(500).send({ success: false, error: \"Error disabling group\" })\n    }\n\n    groupEvent.emit('group.update', updatedGroup);\n    res.status(200).send(updatedGroup);\n  })\n\n})\n\n// router.put('/:groupid', function (req, res) {\n\n//   winston.debug(req.body);\n\n//   var update = {};\n  \n//     update.name = req.body.name;\n//     update.members = req.body.members;\n//     update.trashed = req.body.trashed;\n  \n\n//   Group.findByIdAndUpdate(req.params.groupid, update, { new: true, upsert: true }, function (err, updatedGroup) {\n//     if (err) {\n//       winston.error('Error putting the group ', err);\n//       return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//     }\n\n//     groupEvent.emit('group.update', updatedGroup);\n//     res.json(updatedGroup);\n//   });\n// });\n\nrouter.delete('/:groupid', async (req, res) => {\n\n  let id_project = req.projectid;\n  let group_id = req.params.groupid;\n\n  const isInDepartment = await departmentService.isGroupInProjectDepartment(id_project, group_id).catch((err) => {\n    winston.error(\"Error checking if group belongs to the department: \", err);\n    return res.status(500).send({ success: false, error: \"Unable to verify group-department association due to an error\" })\n  })\n\n  if (isInDepartment) {\n    winston.verbose(\"The group \" + group_id + \" belongs to a department and cannot be deleted\");\n    return res.status(403).send({ success: false, error: \"Unable to delete a group associated with a department\" })\n  }\n\n  if (req.query.force === \"true\") {\n    \n    Group.findByIdAndRemove(group_id, function (err, group) {\n      if (err) {\n        winston.error('Error removing the group ', err);\n        return res.status(500).send({ success: false, msg: 'Error deleting group' });\n      }\n      winston.debug(\"Physically removed group\", group);\n      // nn funziuona perchje nn c'è id_project\n      groupEvent.emit('group.delete', group);\n      res.status(200).send(group);\n    });\n\n  } else {\n\n    Group.findByIdAndUpdate(group_id, { enabled: false, trashed: true }, { new: true }, function (err, group) {\n        if (err) {\n          winston.error('Error removing the group ', err);\n          return res.status(500).send({ success: false, msg: 'Error deleting group' });\n        }\n\n        winston.debug(\"Group logical deleted\", group);\n\n        groupEvent.emit('group.update', group);\n\n        res.status(200).send({ success: true, message: \"Group successfully deleted\"})\n      }\n    );\n  }\n\n});\n\n\nrouter.get('/:groupid', function (req, res) {\n\n  winston.debug(req.body);\n\n  Group.findById(req.params.groupid, function (err, group) {\n    if (err) {\n      winston.error('Error getting the group ', err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!group) {\n      winston.warn('group not found', err);\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(group);\n  });\n});\n\n\n\nrouter.get('/', function (req, res) {\n\n  winston.debug(\"req projectid\", req.projectid);\n\n  var query = { \"id_project\": req.projectid, trashed: false };\n\n  if (req.query.member) {\n    query.members = { $in : req.query.member }\n  }\n  \n  winston.debug(\"query\", query);\n  \n  Group.find(query, function (err, groups) {        \n    if (err) {\n      winston.error('Error getting the group ', err);\n      return next(err);\n    }\n    res.json(groups);\n  });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/images.js",
    "content": "var express = require('express');\nconst multer  = require('multer');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar winston = require('../config/winston');\nvar pathlib = require('path');\n\nvar router = express.Router();\n\n\nconst sharp = require('sharp');\n\n\n\n\nconst FileGridFsService = require('../services/fileGridFsService.js');\nconst faq_kb = require('../models/faq_kb');\nconst project_user = require('../models/project_user');\nconst roleConstants = require('../models/roleConstants');\n\nconst fileService = new FileGridFsService(\"images\");\n\n\n\n\n\nconst fileFilter = (req, file, cb) => {\n  if (file.mimetype == 'image/jpeg' || file.mimetype == 'image/png' \n      || file.mimetype == 'image/gif'|| file.mimetype == 'image/vnd.microsoft.icon'\n      || file.mimetype == 'image/webp') {\n      cb(null, true);\n  } else {\n      cb(null, false);\n  }\n}\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;\n  winston.debug(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.debug(\"Max upload file size is infinity\");\n}\n\n\n// const bodymiddleware = function(req, res, next) {\n//   winston.info(\"YYYYYY req.body.folder:\"+req.body.folder);\n//   winston.info(\"YYYYYY req.body:\",req.body);\n//   next();\n// }\n\n\nconst upload = multer({ storage: fileService.getStorage(\"images\"), fileFilter: fileFilter, limits: uploadlimits });\n\n/*\ncurl -u andrea.leo@f21.it:123456 \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n  http://localhost:3000/images/users/\n\n\n  curl -u andrea.leo@frontiere21.it:123 \\ \n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n  https://tiledesk-server-pre.herokuapp.com/images/users/\n\n  */\n\n// DEPRECATED FROM VERSION 2.14.24\n// router.post('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken],\n// // bodymiddleware, \n// upload.single('file'), (req, res, next) => {\n//   try {\n//     // winston.info(\"req.query.folder1:\"+req.body.folder);\n\n//     var folder = req.folder || \"error\";\n//     winston.debug(\"folder:\"+folder);\n\n//      var destinationFolder = 'uploads/users/' + req.user.id + \"/images/\" + folder +\"/\";\n//      winston.debug(\"destinationFolder\",destinationFolder);\n\n//      var thumFilename = destinationFolder+'thumbnails_200_200-' + req.file.originalname;\n\n\n//      fileService.getFileDataAsBuffer(req.file.filename).then(function(buffer) {\n\n//       sharp(buffer).resize(200, 200).toBuffer((err, resizeImage, info) => {\n//         //in prod nn genera thumb\n//         if (err) { winston.error(\"Error generating thumbnail\", err); }\n//         fileService.createFile ( thumFilename, resizeImage, undefined, undefined);\n//       });\n\n//       return res.status(201).json({\n//           message: 'Image uploded successfully',\n//           filename: encodeURIComponent(req.file.filename),\n//           thumbnail: encodeURIComponent(thumFilename)\n//       });\n//     });\n//   } catch (error) {\n//     winston.error('Error uploading user image.',error);\n//     return res.status(500).send({success: false, msg: 'Error uploading user image.'});\n//   }\n// });\n\n\n\n\n\n\nconst uploadFixedFolder = multer({ storage: fileService.getStorageFixFolder(\"images\"), fileFilter: fileFilter, limits: uploadlimits });\n\n/*\ncurl -v -X PUT -u andrea.leo@f21.it:123456 \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n  http://localhost:3000/images/users/\n\n\n  curl -v -X PUT -u andrea.leo@frontiere21.it:123 \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n    https://tiledesk-server-pre.herokuapp.com/images/users/\n\n  */\n\n// DEPRECATED FROM VERSION 2.14.24\n// router.put('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken],\n// // bodymiddleware, \n// uploadFixedFolder.single('file'), (req, res, next) => {\n//   try {\n//     winston.debug(\"/users/folder\");\n//     // winston.info(\"req.query.folder1:\"+req.body.folder);\n\n//     // var folder = req.folder || \"error\";\n//     // winston.info(\"folder:\"+folder);\n\n//     if (req.upload_file_already_exists) {\n//       winston.warn('Error uploading photo image, file already exists',req.file.filename );\n//       return res.status(409).send({success: false, msg: 'Error uploading user image, file already exists'});\n//     }\n\n//      var destinationFolder = 'uploads/users/' + req.user.id + \"/images/\";\n//      winston.debug(\"destinationFolder\",destinationFolder);\n\n//      var thumFilename = destinationFolder+'thumbnails_200_200-' + req.file.originalname;\n\n\n//      fileService.getFileDataAsBuffer(req.file.filename).then(function(buffer) {\n\n//       sharp(buffer).resize(200, 200).toBuffer((err, resizeImage, info) => {\n//         //in prod nn genera thumb\n//         if (err) { winston.error(\"Error generating thumbnail\", err); }\n//         fileService.createFile ( thumFilename, resizeImage, undefined, undefined);\n//       });\n\n//       return res.status(201).json({\n//           message: 'Image uploded successfully',\n//           filename: encodeURIComponent(req.file.filename),\n//           thumbnail: encodeURIComponent(thumFilename)\n//       });\n//     });\n//   } catch (error) {\n//     winston.error('Error uploading user image.',error);\n//     return res.status(500).send({success: false, msg: 'Error uploading user image.'});\n//   }\n// });\n\n\n\n\n\n\n\n\n\nconst uploadAvatar= multer({ storage: fileService.getStorageAvatar(\"images\"), fileFilter: fileFilter, limits: uploadlimits });\n\n/*\ncurl -v -X PUT -u andrea.leo@f21.it:123456 \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n  http://localhost:3000/images/users/photo\n\n  curl -v -X PUT -u andrea.leo@f21.it:123456 \\\n  -F \"file=@/Users/andrealeo/Downloads/aa2.jpg\" \\\n  http://localhost:3000/images/users/photo\n\n  curl -v -X PUT -u andrea.leo@frontiere21.it:258456td \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n    https://tiledesk-server-pre.herokuapp.com/images/users/photo\n\n  */\n// DEPRECATED FROM VERSION 2.14.24\n// router.put('/users/photo', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken],\n// // bodymiddleware, \n// uploadAvatar.single('file'), async (req, res, next) => {\n//   try {\n//     winston.debug(\"/users/photo\");\n\n//     if (req.upload_file_already_exists) {\n//       winston.warn('Error uploading photo image, file already exists',req.file.filename );\n//       return res.status(409).send({success: false, msg: 'Error uploading photo image, file already exists'});\n//     }\n\n//     let userid = req.user.id;\n//     let bot_id;\n//     let entity_id = userid;\n\n//     // if (req.query.user_id) {\n//     //   userid = req.query.user_id;\n//     // }\n    \n//     if (req.query.bot_id) {\n//       bot_id = req.query.bot_id;\n\n//       let chatbot = await faq_kb.findById(bot_id).catch((err) => {\n//         winston.error(\"Error finding bot \", err);\n//         return res.status(500).send({ success: false, error: \"Unable to find chatbot with id \" + bot_id });\n//       })\n\n//       if (!chatbot) {\n//         return res.status(404).send({ success: false, error: \"Chatbot not found\" })\n//       }\n\n//       let id_project = chatbot.id_project;\n\n//       let puser = await project_user.findOne({ id_user: userid, id_project: id_project }).catch((err) => {\n//         winston.error(\"Error finding project user: \", err);\n//         return res.status(500).send({ success: false, error: \"Unable to find project user for user \" + userid + \"in project \" + id_project });\n//       })\n//       if (!puser) {\n//         winston.warn(\"User\" + userid + \"don't belongs the project \" + id_project);\n//         return res.status(401).send({ success: false, error: \"You don't belong the chatbot's project\" })\n//       }\n\n//       if ((puser.role !== roleConstants.ADMIN) && (puser.role !== roleConstants.OWNER)) {\n//         winston.warn(\"User with role \" + puser.role + \"can't modify the chatbot\");\n//         return res.status(403).send({ success: false, error: \"You don't have the role required to modify the chatbot\" });\n//       }\n\n//       entity_id = bot_id;\n//     }\n\n//      var destinationFolder = 'uploads/users/' + entity_id + \"/images/\";\n//      winston.debug(\"destinationFolder:\"+destinationFolder);\n\n//      var thumFilename = destinationFolder+'thumbnails_200_200-photo.jpg';\n\n//      winston.debug(\"req.file.filename:\"+req.file.filename);\n//      fileService.getFileDataAsBuffer(req.file.filename).then(function(buffer) {\n\n//       sharp(buffer).resize(200, 200).toBuffer((err, resizeImage, info) => {\n//         //in prod nn genera thumb\n//         if (err) { winston.error(\"Error generating thumbnail\", err); }\n//         fileService.createFile ( thumFilename, resizeImage, undefined, undefined);\n//       });\n\n//       return res.status(201).json({\n//           message: 'Image uploded successfully',\n//           filename: encodeURIComponent(req.file.filename),\n//           thumbnail: encodeURIComponent(thumFilename)\n//       }); \n//     });\n//   } catch (error) {\n//     winston.error('Error uploading user image.',error);\n//     return res.status(500).send({success: false, msg: 'Error uploading user image.'});\n//   }\n// });\n\n\n\n\n\n\n/*\ncurl -v -X DELETE -u andrea.leo@f21.it:123456 \\\n  http://localhost:3000/images/users/?path=uploads%2Fusers%2F609bf8157bf5ca7ef7160197%2Fimages%2Ftest.jpg\n\ncurl -v -X DELETE  -u andrea.leo@frontiere21.it:123 \\\n   https://tiledesk-server-pre.herokuapp.com/images/users/?path=uploads%2Fusers%2F5aaa99024c3b110014b478f0%2Fimages%2Fphoto.jpg\n \n*/\n\n// router.delete('/users', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], (req, res, next) => {\n//   try {\n//     winston.debug(\"delete /users\");\n\n//     let path = req.query.path;\n//     winston.debug(\"path:\"+path);\n\n//     // TODO later if enabled there is problem when admin delete a bot image\n//     // if (path.indexOf(\"/\"+req.user.id+\"/\")==-1) {\n//     //   winston.warn('Permission denied to delete image:'+path);\n//     //   return res.status(403).send({success: false, msg: 'Permission denied to delete image:'+path});\n//     // }\n\n//     let filename = pathlib.basename(path);\n//     winston.debug(\"filename:\"+filename);\n\n//     if (!filename) {\n//       winston.warn('Error delete image. No filename specified:'+path);\n//       return res.status(500).send({success: false, msg: 'Error delete image. No filename specified:'+path});\n//     }\n\n  \n\n//     fileService.deleteFile(path).then(function(data) {\n\n//       let thumbFilename = 'thumbnails_200_200-'+filename;\n//       winston.debug(\"thumbFilename:\"+thumbFilename);\n\n//       let thumbPath = path.replace(filename,thumbFilename);\n//       winston.debug(\"thumbPath:\"+thumbPath);\n\n//       fileService.deleteFile(thumbPath).then(function(data) {\n//         winston.debug(\"thumbFilename deleted:\"+thumbPath);\n//       }).catch(function(error) {\n//         winston.error('Error deleting thumbnail image.',error);\n//       });\n\n//       return res.status(200).json({\n//           message: 'Image deleted successfully',\n//           filename: encodeURIComponent(data.filename)\n//       });\n//     }).catch(function(error) {\n//       winston.error('Error deleting image.',error);\n//       return res.status(500).send({success: false, msg: 'Error deleting image.'});\n//     });\n\n//   } catch (error) {\n//     winston.error('Error deleting image.',error);\n//     return res.status(500).send({success: false, msg: 'Error deleting image.'});\n//   }\n// });\n\n\n/*\ncurl \\\n  -F \"userid=1\" \\\n  -F \"filecomment=This is an image file\" \\\n  -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" \\\n  http://localhost:3000/images/public/\n\n  */\n\n  \n/*\n{% api-method method=\"post\" host=\"https://api.tiledesk.com\" path=\"/v2/images/public\" %}\n{% api-method-summary %}\nUpload a public image\n{% endapi-method-summary %}\n\n{% api-method-description %}\nAllows to upload an image without autentication\n{% endapi-method-description %}\n\n{% api-method-spec %}\n{% api-method-request %}\n{% api-method-path-parameters %}\n{% endapi-method-path-parameters %}\n\n{% api-method-headers %}\n\n{% api-method-parameter name=\"Content-Type\" type=\"string\" required=true %}\nuse \"multipart/form-data\" value\n{% endapi-method-parameter %}\n{% endapi-method-headers %}\n\n{% api-method-body-parameters %}\n{% api-method-parameter name=\"file\" type=\"binary\" required=true %}\nthe image binary file\n{% endapi-method-parameter %}\n\n{% endapi-method-body-parameters %}\n{% endapi-method-request %}\n\n{% api-method-response %}\n{% api-method-response-example httpCode=200 %}\n{% api-method-response-example-description %}\n\n{% endapi-method-response-example-description %}\n\n```text\n  {\n    \"message\":\"File uploded successfully\",\n    \"filename\":\"uploads/public/images/a96e1fc5-a331-4d3d-bb03-2244cbf71640/test.jpg\"\n  }\n```\n{% endapi-method-response-example %}\n{% endapi-method-response %}\n{% endapi-method-spec %}\n{% endapi-method %}\n\nExample:\n\n```text\ncurl -v -X POST -H 'Content-Type: multipart/form-data' -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" https://api.tiledesk.com/v2/images/public\n```\n*/\n\n/*\ncurl -v -X POST -H 'Content-Type: multipart/form-data' -F \"file=@/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test.jpg\" https://tiledesk-server-pre.herokuapp.com/images/public/\n*/\n\n// DEPRECATED FROM VERSION 2.14.24\n// router.post('/public', upload.single('file'), (req, res, next) => {\n//   try {\n//      winston.debug(\"req\",req);\n//      var folder = req.folder || \"error\";\n//      winston.debug(\"folder\",folder);\n\n//      var destinationFolder = \"uploads/public/images/\" + folder +\"/\";\n//      winston.debug(\"destinationFolder\",destinationFolder);\n\n//      winston.debug(\"req.file.filename\",req.file.filename);\n\n//      var thumFilename = destinationFolder+'thumbnails_200_200-' + req.file.originalname;          \n\n\n//      fileService.getFileDataAsBuffer(req.file.filename).then(function(buffer) {\n\n//         sharp(buffer).resize(200, 200).toBuffer((err, resizeImage, info) => {\n//             if (err) { winston.error(\"Error generating thumbnail\", err); }\n//             fileService.createFile ( thumFilename, resizeImage, undefined, undefined);\n//         });\n\n\n//         return res.status(201).json({\n//             message: 'Image uploded successfully',\n//             filename: encodeURIComponent(req.file.filename),\n//             thumbnail: encodeURIComponent(thumFilename)\n//         });\n//       });\n      \n\n     \n//   } catch (error) {\n//     winston.error('Error deleting public image.',error);\n//     return res.status(500).send({success: false, msg: 'Error deleting public image.'});\n//   }\n// });\n\n\n// router.use('/uploads', express.static(path.join(__dirname, '/uploads')));\n// router.use('/uploads', function timeLog(req, res, next) {\n//   winston.debug('Time: ', Date.now());\n//   var a = express.static(path.join(__dirname, '/uploads'))\n//   winston.debug('Time2: ', a);\n//   return a;\n// }, express.static(path.join(__dirname, '/uploads')));\n\n// curl -X GET 'http://localhost:3000/images/thumbnails?width=10&height=10'\nrouter.get('/thumbnails', (req, res) => {\n  // Extract the query-parameter\n  const widthString = req.query.width\n  const heightString = req.query.height\n  const format = req.query.format\n\n  // Parse to integer if possible\n  let width, height\n  if (widthString) {\n    width = parseInt(widthString)\n  }\n  if (heightString) {\n    height = parseInt(heightString)\n  }\n  // Set the content-type of the response\n  res.type(`image/${format || 'png'}`)\n\n  // Get the resized image\n  sharp('test.jpg').resize(width, height).pipe(res)\n  // sharp('nodejs.png').resize(format, width, height).pipe(res)\n})\n\n\n// curl -X GET 'http://localhost:3000/images?path=123'\nrouter.get(\"/\", async (req, res) => {\n  winston.debug('path', req.query.path);\n\n  if (req.query.as_attachment) {\n    res.set({ \"Content-Disposition\": \"attachment; filename=\\\"\"+req.query.path+\"\\\"\" });\n\n  }\n\n\n  try {\n    let file = await fileService.find(req.query.path);\n    // console.log(\"file\", file);\n\n    res.set({ \"Content-Length\": file.length});\n    res.set({ \"Content-Type\": file.contentType});\n\n  } catch (e) {\n    if (e.code == \"ENOENT\") {\n      winston.debug('Image not found: '+req.query.path);\n      return res.status(404).send({success: false, msg: 'Image not found.'});\n    }else {\n      winston.error('Error getting the image', e);\n      return res.status(500).send({success: false, msg: 'Error getting image.'});\n    }      \n  }\n\n  fileService.getFileDataAsStream(req.query.path).on('error', (e)=> {\n      if (e.code == \"ENOENT\") {\n        winston.debug('Image not found: '+req.query.path);\n        return res.status(404).send({success: false, msg: 'Image not found.'});\n      }else {\n        winston.error('Error getting the image', e);\n        return res.status(500).send({success: false, msg: 'Error getting image.'});\n      }      \n    }).pipe(res);\n  // } catch (e) {\n  //   winston.error('Error getting the image', e);\n  //   return res.status(500).send({success: false, msg: 'Error getting image.'});\n  // }\n  \n  // const file = gfs\n  //   .find({\n  //     filename: req.query.path\n  //   })\n  //   .toArray((err, files) => {\n  //     if (!files || files.length === 0) {\n  //       return res.status(404).json({\n  //         err: \"no files exist\"\n  //       });\n  //     }\n  //     gfs.openDownloadStreamByName(req.query.path).pipe(res);\n  //   });\n});\n\n// router.get('/', \n//  function (req, res) {\n//     res.send('{\"success\":true}');\n// });\n  \n  \n  // router.get('/', \n  // expresssharp.expressSharp({\n  //     imageAdapter: new expresssharp.FsAdapter(path.join(__dirname, 'images')),\n  //   })\n  // );\n  \n\nmodule.exports = router;"
  },
  {
    "path": "routes/index.js",
    "content": "var express = require('express');\nvar router = express.Router();\n\n/* GET home page. */\nrouter.get('/', function(req, res, next) {\n  res.render('index', { title: 'Tiledesk' });\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/integration.js",
    "content": "let express = require('express');\nlet router = express.Router();\nvar winston = require('../config/winston');\nlet Integration = require('../models/integrations');\nconst cacheEnabler = require('../services/cacheEnabler');\nvar cacheUtil = require('../utils/cacheUtil');\nconst integrationEvent = require('../event/integrationEvent');\n\n\n// Get all integration for a project id \nrouter.get('/', async (req, res) => {\n\n    let id_project = req.projectid;\n    winston.debug(\"Get all integration for the project \" + id_project);\n\n    let i = Integration.find({ id_project: id_project });\n    if (cacheEnabler.integrations) {\n        // cacheUtil.longTTL is 1 hour (default), evaluate 1 month (2592000 s)\n        i.cache(cacheUtil.longTTL, \"project:\" + id_project + \":integrations\");\n        winston.debug('integration cache enabled for get all integrations');\n    }\n    i.exec((err, integrations) => {\n        if (err) {\n            winston.error(\"Error getting integrations: \", err);\n            return res.status(500).send({ success: false, message: \"Error getting integrations \"});\n        }\n        res.status(200).send(integrations);\n    })\n    // without cache\n    // Integration.find({ id_project: id_project }, (err, integrations) => {\n    //     if (err) {\n    //         console.error(\"Error finding all integrations for the project \" + id_project + \" - err: \" + err);\n    //         return res.status(404).send({ success: false, err: err })\n    //     }\n    //     console.log(\"Integrations found: \", integrations);\n    //     res.status(200).send(integrations);\n    // })\n})\n\n// Get one integration\nrouter.get('/:integration_id', async (req, res) => {\n    \n    let integration_id = req.params.integration_id;\n    winston.debug(\"Get integration with id \" + integration_id);\n\n    Integration.findById(integration_id, (err, integration) => {\n        if (err) {\n            winston.error(\"Error find integration by id: \", err);\n            return res.status(404).send({ success: false, err: err });\n        }\n        res.status(200).send(integration);\n    })\n\n})\n\nrouter.get('/name/:integration_name', async (req, res) => {\n\n    let id_project = req.projectid;\n    winston.debug(\"Get all integration for the project \" + id_project);\n\n    let integration_name = req.params.integration_name;\n    winston.debug(\"Get integration with id \" + integration_name);\n\n    Integration.findOne({ id_project: id_project, name: integration_name }, (err, integration) => {\n        if (err) {\n            winston.error(\"Error find integration by name: \", err);\n            return res.status(404).send({ success: false, err: err });\n        }\n\n        if (!integration) {\n            winston.debug(\"Integration not found\");\n            return res.status(200).send(\"Integration not found\");\n        }\n        \n        res.status(200).send(integration);\n    })\n})\n\n// Add new integration\nrouter.post('/', async (req, res) => {\n\n    let id_project = req.projectid;\n    winston.debug(\"Add new integration \", req.body);\n\n\n    let newIntegration = {\n        id_project: id_project,\n        name: req.body.name\n    }\n    if (req.body.value) {\n        newIntegration.value = req.body.value;\n    }\n\n    Integration.findOneAndUpdate({ id_project: id_project, name: req.body.name },  newIntegration, { new: true, upsert: true, setDefaultsOnInsert: false}, (err, savedIntegration) => {\n        if (err) {\n            winston.error(\"Error creating new integration \", err);\n            return res.status(404).send({ success: false, err: err })\n        }\n\n        winston.debug(\"New integration created: \", savedIntegration);\n        \n        Integration.find({ id_project: id_project }, (err, integrations) => {\n            if (err) {\n                winston.error(\"Error getting all integrations\");\n            } else {\n                integrationEvent.emit('integration.update', integrations, id_project);\n            }\n        })\n        \n        res.status(200).send(savedIntegration);\n    })\n\n    // let newIntegration = new Integration({\n    //     id_project: id_project,\n    //     name: req.body.name,\n    //     value: req.body.value\n    // })\n\n    // newIntegration.save((err, savedIntegration) => {\n    //     if (err) {\n    //         console.error(\"Error creating new integration \", err);\n    //         return res.status(404).send({ success: false, err: err })\n    //     }\n\n    //     console.log(\"New integration created: \", savedIntegration);\n\n    //     Integration.find({ id_project: id_project }, (err, integrations) => {\n    //         if (err) {\n    //             console.error(\"Error getting all integrations\");\n    //         } else {\n    //             console.log(\"emit integration.create event\")\n    //             integrationEvent.emit('integration.create', integrations, id_project);\n    //         }\n\n    //     })\n        \n    //     res.status(200).send(savedIntegration);\n\n    // })\n    \n})\n\nrouter.put('/:integration_id', async (req, res) => {\n\n    let id_project = req.projectid;\n    let integration_id = req.params.integration_id;\n    \n    let update = {};\n    if (req.body.name != undefined) {\n        update.name = req.body.name;\n    }\n    if (req.body.value != undefined) {\n        update.value = req.body.value\n    }\n\n    Integration.findByIdAndUpdate(integration_id, update, { new: true, upsert: true }, (err, savedIntegration) => {\n        if (err) {\n            winston.error(\"Error find by id and update integration: \", err);\n            return res.status({ success: false, error: err })\n        }\n\n        Integration.find({ id_project: id_project }, (err, integrations) => {\n            if (err) {\n                winston.error(\"Error getting all integrations\");\n            } else {\n                integrationEvent.emit('integration.update', integrations, id_project);\n            }\n        })\n\n        res.status(200).send(savedIntegration);\n    })\n})\n\nrouter.delete('/:integration_id', async (req, res) => {\n\n    let id_project = req.projectid;\n    let integration_id = req.params.integration_id;\n\n    Integration.findByIdAndDelete(integration_id, (err, result) => {\n        if (err) {\n            winston.error(\"Error find by id and delete integration: \", err);\n            return res.status({ success: false, error: err })\n        }\n\n        Integration.find({ id_project: id_project }, (err, integrations) => {\n            if (err) {\n                winston.error(\"Error getting all integrations\");\n            } else {\n                integrationEvent.emit('integration.update', integrations, id_project);\n            }\n        })\n\n        res.status(200).send({ success: true, messages: \"Integration deleted successfully\"});\n\n    })\n})\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/jwt.js",
    "content": "\n'use strict';\n\n// Modules imports\nconst express = require('express');\nvar router = express.Router();\nvar config = require('../config/database'); \nvar jwt = require('jsonwebtoken');\nvar validtoken = require('../middleware/valid-token');\nvar requestUtil = require('../utils/requestUtil');\nvar Project = require('../models/project');\nvar winston = require('../config/winston');\n\n// deprecated??\nrouter.post('/decode', validtoken, function (req, res) {\n\n    // var project_id = req.query.project_id;\n\n    // winston.debug(\"project_id\", project_id);\n\n    // if (!project_id) {\n    //     winston.error(\"project_id parameter is required\");\n    //     res.status(400).send({ success: false, msg: \"project_id parameter is required\" });\n    // }\n\n\n    return  Project.findOne({_id: req.projectid, status: 100}, '+jwtSecret',function(err, project) {\n      if (err) {\n        winston.error('Error finding project', err);\n        return res.status(500).send({ success: false, msg: 'Error finding project.' });\n     }\n    \n      winston.debug('project', project);\n\n      if (!project) {\n          return res.status(404).send({ success: false, msg: 'Project not found' });\n      }\n\n      if (!project.jwtSecret) {\n        return res.status(404).send({ success: false, msg: 'Project hasnt  a jwtSecret' });\n      }\n\n\n        try {\n          winston.debug(\"requestUtil\", requestUtil);\n            var token = requestUtil.getToken(req.headers);\n\n\n            winston.debug(\"token\", token);\n            // verify a token symmetric - synchronous\n            var decoded = jwt.verify(token, project.jwtSecret);\n            \n            winston.debug(\"decoded\", decoded);\n      \n\n            if(!decoded.iat ) {                  \n                winston.debug(\"token.iat is required\");\n                return res.status(401).send({ success: false, msg: 'Authentication failed. Token iat is required'});\n            }\n            if(!decoded.exp ) {                  \n                winston.debug(\"token.exp is required\");\n                return res.status(401).send({ success: false, msg: 'Authentication failed. Token exp is required'});\n            }\n            if(decoded.exp - decoded.iat  > 600  ) {                  \n                winston.debug(\"The value of exp is permitted to be up to a maximum of 10 minutes from the iat value\");\n                return res.status(401).send({ success: false, msg: 'Authentication failed. The value of exp is permitted to be up to a maximum of 10 minutes from the iat value'});\n            }\n\n\n            return res.json({decoded: decoded});\n          \n    \n        } catch(err) {\n            winston.error(\"Authentication failed\", err);\n            return res.status(401).send({ success: false, msg: 'Authentication failed', err: err });\n        }  \n\n    });\n    \n  \n  });\n\n  router.post('/generatetestjwt', validtoken, function (req, res) {\n    \n    winston.debug(\"req.body\", req.body);\n    return  Project.findOne({_id: req.projectid, status: 100}, '+jwtSecret',function(err, project) {    \n        if (err) {\n          winston.error('Error finding project', err);\n          return res.status(500).send({ success: false, msg: 'Error finding project.' });\n       }\n      \n        winston.debug('project', project);\n  \n        if (!project) {\n            return res.status(404).send({ success: false, msg: 'Project not found' });\n        }\n  \n        if (!project.jwtSecret) {\n          return res.status(404).send({ success: false, msg: 'Project hasnt  a jwtSecret' });\n        }\n\n        var token = jwt.sign(req.body, project.jwtSecret,{ expiresIn: 300 });\n        // var token = jwt.sign(req.body, project.jwtSecret);\n\n\n     \n        res.json({ success: true, token: 'JWT ' + token });\n    });\n\n  });\n\n\n  module.exports = router;"
  },
  {
    "path": "routes/kb.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\nvar multer = require('multer')\nvar upload = multer()\nconst path = require('path');\nconst JobManager = require('../utils/jobs-worker-queue-manager/JobManagerV2');\nvar configGlobal = require('../config/global');\nvar mongoose = require('mongoose');\nvar parsecsv = require(\"fast-csv\");\n\nvar { Namespace, KB } = require('../models/kb_setting');\nconst faq = require('../models/faq');\nconst faq_kb = require('../models/faq_kb');\n\nconst { MODELS_MULTIPLIER } = require('../utils/aiUtils');\nconst { parseStringArrayField } = require('../utils/arrayUtil');\nconst { kbTypes } = require('../models/kbConstants');\nconst Sitemapper = require('sitemapper');\n\nconst aiService = require('../services/aiService');\nconst aiManager = require('../services/aiManager');\nconst integrationService = require('../services/integrationService');\n\nconst AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;\nconst JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';\nconst JOB_TOPIC_EXCHANGE_HYBRID = process.env.JOB_TOPIC_EXCHANGE_TRAIN_HYBRID || 'tiledesk-trainer-hybrid';\nconst KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';\nconst PINECONE_RERANKING = process.env.PINECONE_RERANKING === true || process.env.PINECONE_RERANKING === \"true\";\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\n\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;\n  winston.debug(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.debug(\"Max upload file size is infinity\");\n}\nvar upload = multer({limits: uploadlimits});\n\n\nlet jobManager = new JobManager(AMQP_MANAGER_URL, {\n  debug: false,\n  topic: JOB_TOPIC_EXCHANGE,\n  exchange: JOB_TOPIC_EXCHANGE\n})\n\njobManager.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"KbRoute - ConnectPublisher done with status: \", status);\n  }\n})\n\nlet jobManagerHybrid = new JobManager(AMQP_MANAGER_URL, {\n  debug: false,\n  topic: JOB_TOPIC_EXCHANGE_HYBRID,\n  exchange: JOB_TOPIC_EXCHANGE_HYBRID\n})\n\njobManagerHybrid.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"KbRoute - ConnectPublisher done with status: \", status);\n  }\n})\n\nlet default_preview_settings = {\n  model: 'gpt-4o',\n  max_tokens: 256,\n  temperature: 0.7,\n  top_k: 4,\n  alpha: 0.5,\n  context: null\n}\n\nconst default_engine = require('../config/kb/engine');\nconst default_engine_hybrid = require('../config/kb/engine.hybrid');\nconst default_embedding = require('../config/kb/embedding');\nconst PromptManager = require('../config/kb/prompt/rag/PromptManager');\nconst situatedContext = require('../config/kb/situatedContext');\n\nconst ragPromptManager = new PromptManager(path.join(__dirname, '../config/kb/prompt/rag'));\n\nconst RAG_CONTEXT_ENV_OVERRIDES = {\n  \"gpt-3.5-turbo\":       process.env.GPT_3_5_CONTEXT,\n  \"gpt-4\":               process.env.GPT_4_CONTEXT,\n  \"gpt-4-turbo-preview\": process.env.GPT_4T_CONTEXT,\n  \"gpt-4o\":              process.env.GPT_4O_CONTEXT,\n  \"gpt-4o-mini\":         process.env.GPT_4O_MINI_CONTEXT,\n  \"gpt-4.1\":             process.env.GPT_4_1_CONTEXT,\n  \"gpt-4.1-mini\":        process.env.GPT_4_1_MINI_CONTEXT,\n  \"gpt-4.1-nano\":        process.env.GPT_4_1_NANO_CONTEXT,\n  \"gpt-5\":               process.env.GPT_5_CONTEXT,\n  \"gpt-5-mini\":          process.env.GPT_5_MINI_CONTEXT,\n  \"gpt-5-nano\":          process.env.GPT_5_NANO_CONTEXT,\n  \"general\":             process.env.GENERAL_CONTEXT\n};\n\n/** RAG system prompt per modello: file in config/kb/prompt/rag, sovrascrivibili via env (come prima). */\nfunction getRagContextTemplate(modelName) {\n  const envOverride = RAG_CONTEXT_ENV_OVERRIDES[modelName];\n  if (envOverride) {\n    return envOverride;\n  }\n  if (!PromptManager.modelMap[modelName] && process.env.GENERAL_CONTEXT) {\n    return process.env.GENERAL_CONTEXT;\n  }\n  return ragPromptManager.getPrompt(modelName);\n}\n\nfunction normalizeEmbedding(embedding) {\n  const normalizedEmbedding = (embedding && typeof embedding.toObject === 'function')\n    ? embedding.toObject()\n    : (embedding || default_embedding);\n  return { ...normalizedEmbedding };\n}\n\nfunction normalizeSituatedContext() {\n  return situatedContext.enable\n    ? {\n      ...situatedContext,\n      api_key: process.env.SITUATED_CONTEXT_API_KEY || process.env.GPTKEY\n    }\n    : undefined;\n}\n\n/**\n* ****************************************\n* Proxy Section - Start\n* ****************************************\n*/\nrouter.post('/scrape/single', async (req, res) => {\n\n  let project_id = req.projectid;\n\n  let data = req.body;\n  winston.debug(\"/scrape/single data: \", data)\n  \n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, data.namespace);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  } \n  \n  if (data.type === \"sitemap\") {\n\n    const urls = await aiManager.fetchSitemap(data.source).catch((err) => {\n      winston.error(\"Error fetching sitemap: \", err);\n      return res.status(500).send({ success: false, error: err });\n    })\n\n    if (urls.length === 0) {\n      return res.status(400).send({ success: false, error: \"No url found on sitemap\" });\n    }\n\n    let sitemapKb;\n    try {\n      sitemapKb = await KB.findById(data.id);\n    } catch (err) {\n      winston.error(\"Error finding sitemap content with id \" +  data.id);\n      return res.status(500).send({ success: false, error: \"Error finding sitemap content with id \" + data.id });\n    }\n\n    if (!sitemapKb) {\n      return res.status(404).send({ success: false, error: \"Content not found with id \" + data.id });\n    }\n    \n    let existingKbs;\n    try {\n      existingKbs = await KB.find({ id_project: project_id, namespace: data.namespace, sitemap_origin_id: data.id }).lean().exec();\n    } catch(err) {\n      winston.error(\"Error finding existing contents: \", err);\n      return res.status(500).send({ success: false, error: \"Error finding existing sitemap contents\" });\n    }\n\n    const result = await aiManager.foundSitemapChanges(existingKbs, urls).catch((err) => {\n      winston.error(\"Error finding sitemap differecens \", err);\n      return res.status(400).send({ success: false, error: \"Error finding sitemap differecens\" });\n    })\n\n    if (!result) return; // esco qui\n\n    const { addedUrls, removedIds } = result;\n\n    if (removedIds.length > 0) {\n      const idsSet = new Set(removedIds);\n      const kbsToDelete = existingKbs.filter(obj => idsSet.has(obj._id));\n\n      aiManager.removeMultipleContents(namespace, kbsToDelete).catch((err) => {\n        winston.error(\"Error deleting multiple contents: \", err);\n      })\n    }\n\n    if (addedUrls.length > 0) {\n      const options = {\n        sitemap_origin_id: sitemapKb._id,\n        sitemap_origin: sitemapKb.source,\n        scrape_type: sitemapKb.scrape_type,\n        scrape_options: sitemapKb.scrape_options,\n        refresh_rate: sitemapKb.refresh_rate,\n        tags: sitemapKb.tags\n      }\n      aiManager.addMultipleUrls(namespace, addedUrls, options).catch((err) => {\n        winston.error(\"(webhook) error adding multiple urls contents: \", err);\n      })\n    }\n\n    return res.status(200).send({ success: true, message: \"Content queued for reindexing\", added_urls: addedUrls.length, removed_url: removedIds.length });\n  }\n\n  KB.findById(data.id, (err, kb) => {\n    if (err) {\n      winston.error(\"findById error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    else if (!kb) {\n      return res.status(404).send({ success: false, error: \"Unable to find the kb requested\" })\n    }\n    else {\n\n      let json = {\n        id: kb._id,\n        type: kb.type,\n        source: kb.source,\n        content: \"\",\n        namespace: kb.namespace\n      }\n\n      if (kb.content) {\n        json.content = kb.content;\n      }\n\n      if (kb.scrape_type) {\n        json.scrape_type = kb.scrape_type\n      }\n\n      if (kb.scrape_options) {\n        json.parameters_scrape_type_4 = {\n          tags_to_extract: kb.scrape_options.tags_to_extract,\n          unwanted_tags: kb.scrape_options.unwanted_tags,\n          unwanted_classnames: kb.scrape_options.unwanted_classnames\n        }\n      }\n\n      if (kb.tags) {\n        json.tags = kb.tags;\n      }\n\n      json.engine = namespace.engine || default_engine;\n      json.embedding = normalizeEmbedding(namespace.embedding);\n      json.embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n\n      if (namespace.hybrid === true) {\n        json.hybrid = true;\n      }\n\n      const situated_context = normalizeSituatedContext();\n      if (situated_context) {\n        json.situated_context = situated_context;\n      }\n\n      winston.verbose(\"/scrape/single json: \", json);\n\n      if (process.env.NODE_ENV === \"test\") {\n        res.status(200).send({ success: true, message: \"Skip indexing in test environment\", data: json })\n      }\n\n      aiManager.startScrape(json).then((response) => {\n        winston.verbose(\"startScrape response: \", response);\n        res.status(200).send(response);\n      }).catch((err) => {\n        winston.error(\"startScrape err: \", err);\n        res.status(500).send({ success: false, error: err });\n      })\n\n    }\n  })\n\n})\n\nrouter.post('/scrape/status', async (req, res) => {\n\n  let project_id = req.projectid;\n  // (EXAMPLE) body: { id, namespace }\n  let data = req.body;\n  let namespace_id = data.namespace;\n  winston.debug(\"/scrapeStatus req.body: \", req.body);\n\n  let returnObject = false;\n\n  if (req.query &&\n    req.query.returnObject &&\n    (req.query.returnObject === true || req.query.returnObject === 'true')) {\n    returnObject = true;\n  }\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  data.engine = namespace.engine || default_engine;\n\n  aiService.scrapeStatus(data).then(async (response) => {\n\n    winston.debug(\"scrapeStatus response.data: \", response.data);\n\n    let update = {};\n\n    if (response.data.status_code) {\n      // update.status = response.data.status_code;\n      update.status = await aiManager.statusConverter(response.data.status_code)\n\n    }\n\n    KB.findByIdAndUpdate(data.id, update, { new: true }, (err, savedKb) => {\n\n      if (err) {\n        winston.verbose(\"Status was successfully recovered, but the update on the db failed\");\n        winston.error(\"find kb by id and updated error: \", err);\n\n        if (returnObject) {\n          return res.status(206).send({ warning: \"Unable to udpate content on db\", message: \"The original reply was forwarded\", data: response.data });\n        } else {\n          return res.status(200).send(response.data);\n        }\n      }\n\n      if (returnObject) {\n        return res.status(200).send(savedKb);\n      } else {\n        return res.status(200).send(response.data);\n      }\n    })\n\n  }).catch((err) => {\n    winston.error(\"scrapeStatus err: \", err);\n    let status = err.response.status;\n    res.status(status).send({ statusText: err.response.statusText, error: err.response.data.detail });\n  })\n})\n\nrouter.post('/qa', async (req, res) => {\n  let id_project = req.projectid;\n  let publicKey = false;\n  let data = req.body;\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, data.namespace);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n  winston.debug(\"/qa data: \", data);\n\n  let model;\n  try {\n    model = await aiManager.resolveLLMConfig(id_project, data.llm, data.model);\n  } catch (err) {\n    let errorCode = err?.code ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  if (!model.api_key && model.provider === 'openai') {\n    model.api_key = process.env.GPTKEY;\n    publicKey = true;\n  }\n\n  data.model = model;\n\n  let obj = { createdAt: new Date() };\n\n  let quoteManager = req.app.get('quote_manager');\n  if (publicKey === true) {\n    let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');\n    if (isAvailable === false) {\n      return res.status(403).send({ success: false, message: \"Tokens quota exceeded\", error_code: 13001})\n    }\n  }\n\n  // Check if \"Advanced Mode\" is active. In such case the default_context must be not appended\n  if (!data.advancedPrompt) {\n    const contextTemplate = getRagContextTemplate(data.model.name);\n    if (data.system_context) {\n      data.system_context = data.system_context + \" \\n\" + contextTemplate;\n    } else {\n      data.system_context = contextTemplate;\n    }\n  }\n\n  data.engine = namespace.engine || default_engine;\n  data.embedding = normalizeEmbedding(namespace.embedding);\n  data.embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n\n  if (namespace.hybrid === true) {\n    data.search_type = 'hybrid';\n    \n    if (data.reranking === true) {\n      data.reranker_model = process.env.RERANKING_MODEL || \"cross-encoder/ms-marco-MiniLM-L-6-v2\";\n      \n      if (!data.reranking_multiplier) {\n        data.reranking_multiplier = 3;\n      }\n\n      if ((data.top_k * data.reranking_multiplier) > 100) {\n        let calculatedRerankingMultiplier = Math.floor(100 / data.top_k);\n        if (calculatedRerankingMultiplier < 1) {\n          calculatedRerankingMultiplier = 1;\n        }\n        data.reranking_multiplier = calculatedRerankingMultiplier;\n      }\n    }\n  } \n\n  if (!namespace.hybrid && data.reranking === true && PINECONE_RERANKING) {\n    \n    data.reranking = {\n      \"provider\": \"pinecone\",\n      \"api_key\": process.env.PINECONE_API_KEY,\n      \"model\": process.env.PINECONE_RERANKING_MODEL || process.env.RERANKING_MODEL || \"bge-reranker-v2-m3\"\n    }\n\n    if (!data.reranking_multiplier) {\n      data.reranking_multiplier = 3;\n    }\n\n    if ((data.top_k * data.reranking_multiplier) > 100) {\n      let calculatedRerankingMultiplier = Math.floor(100 / data.top_k);\n      if (calculatedRerankingMultiplier < 1) {\n        calculatedRerankingMultiplier = 1;\n      }\n      data.reranking_multiplier = calculatedRerankingMultiplier;\n    }\n  }\n\n  data.stream = data.stream === true;\n  data.debug = true;\n  delete data.advancedPrompt;\n  winston.verbose(\"ask data: \", data);\n\n  console.log(\"data: \", data);\n\n  if (process.env.NODE_ENV === 'test') {\n    return res.status(200).send({ success: true, message: \"Question skipped in test environment\", data: data });\n  }\n\n  if (data.stream === true) {\n    // Streaming SSE: use askStream and forward only content as JSON SSE events\n    res.status(200);\n    res.setHeader('Content-Type', 'text/event-stream');\n    res.setHeader('Cache-Control', 'no-cache');\n    res.setHeader('Connection', 'keep-alive');\n    res.setHeader('Access-Control-Allow-Origin', '*');\n\n    const sendError = (message) => {\n      try {\n        res.write('data: ' + JSON.stringify({ error: message }) + '\\n\\n');\n      } catch (_) {}\n      res.end();\n    };\n\n    function extractContent(obj) {\n      if (obj.content != null) return obj.content;\n      if (obj.choices && obj.choices[0]) {\n        const c = obj.choices[0];\n        if (c.delta && c.delta.content != null) return c.delta.content;\n        if (c.message && c.message.content != null) return c.message.content;\n      }\n      return null;\n    }\n\n    /** Same JSON shape as non-stream /qa: stream may wrap it in model_used */\n    function normalizeKbQaPayload(obj) {\n      if (obj && typeof obj === 'object' && obj.model_used != null && typeof obj.model_used === 'object') {\n        return obj.model_used;\n      }\n      return obj;\n    }\n\n    /** Flat final payload like non-stream /qa (answer, prompt_token_size, …) */\n    function isMetadataPayload(obj, streamedContent) {\n      if (obj == null || typeof obj !== 'object') return false;\n      if (streamedContent != null && streamedContent !== '') return false;\n      if (typeof obj.prompt_token_size === 'number') return true;\n      if (obj.answer != null) return true;\n      if (obj.sources != null) return true;\n      if (obj.chunks != null) return true;\n      if (obj.content_chunks != null) return true;\n      return false;\n    }\n\n    /** KB stream summary: full_response + model_used (same info as non-stream body, plus envelope) */\n    function isKbStreamCompletedSummary(obj) {\n      if (obj == null || typeof obj !== 'object') return false;\n      if (obj.status === 'completed') return true;\n      if (obj.full_response != null && obj.model_used != null && typeof obj.model_used === 'object') return true;\n      return false;\n    }\n\n    function forwardSsePayload(payload) {\n      if (payload === '[DONE]') return;\n      let obj;\n      try {\n        obj = JSON.parse(payload);\n      } catch (_) {\n        return;\n      }\n\n      if (obj.status === 'started') {\n        return;\n      }\n      if (isKbStreamCompletedSummary(obj)) {\n        res.write('data: ' + JSON.stringify(normalizeKbQaPayload(obj)) + '\\n\\n');\n        return;\n      }\n\n      if (obj.type === 'metadata' || obj.event === 'metadata') {\n        res.write('data: ' + JSON.stringify(normalizeKbQaPayload(obj)) + '\\n\\n');\n        return;\n      }\n      const content = extractContent(obj);\n      if (content != null && content !== '') {\n        res.write('data: ' + JSON.stringify({ content }) + '\\n\\n');\n        return;\n      }\n      const normalized = normalizeKbQaPayload(obj);\n      if (isMetadataPayload(normalized, content)) {\n        res.write('data: ' + JSON.stringify(normalized) + '\\n\\n');\n      }\n    }\n\n    aiService.askStream(data).then((resp) => {\n      const stream = resp.data;\n      let buffer = '';\n\n      stream.on('data', (chunk) => {\n        buffer += chunk.toString();\n        const lines = buffer.split('\\n');\n        buffer = lines.pop() || '';\n\n        for (const line of lines) {\n          const trimmed = line.trim();\n          if (!trimmed.startsWith('data: ')) continue;\n          const payload = trimmed.slice(6);\n          forwardSsePayload(payload);\n        }\n      });\n\n      stream.on('end', () => {\n        const tail = buffer.trim();\n        if (tail) {\n          for (const line of tail.split('\\n')) {\n            const trimmed = line.trim();\n            if (!trimmed.startsWith('data: ')) continue;\n            forwardSsePayload(trimmed.slice(6));\n          }\n        }\n        res.write('data: [DONE]\\n\\n');\n        res.end();\n      });\n\n      stream.on('error', (err) => {\n        winston.error('qa stream err: ', err);\n        sendError(err.message || 'Stream error');\n      });\n\n      res.on('close', () => {\n        if (!res.writableEnded) {\n          stream.destroy();\n        }\n      });\n    }).catch((err) => {\n      winston.error('qa err: ', err);\n      winston.error('qa err.response: ', err.response);\n      const message = (err.response && err.response.data && typeof err.response.data.pipe !== 'function' && err.response.data.detail)\n        ? err.response.data.detail\n        : (err.response && err.response.statusText) || err.message || String(err);\n      if (!res.headersSent) {\n        res.status(err.response && err.response.status ? err.response.status : 500);\n      }\n      sendError(message);\n    });\n    return;\n  }\n\n  aiService.askNamespace(data).then((resp) => {\n    winston.debug(\"qa resp: \", resp.data);\n    let answer = resp.data;\n\n    if (publicKey === true) {\n      let modelKey;\n      if (typeof data.model === 'string') {\n        modelKey = data.model;\n      } else if (data.model && typeof data.model.name === 'string') {\n        modelKey = data.model.name;\n      }\n\n      let multiplier = MODELS_MULTIPLIER[modelKey];\n      if (!multiplier) {\n        multiplier = 1;\n        winston.info(\"No multiplier found for AI model (qa) \" + modelKey);\n      }\n\n      obj.multiplier = multiplier;\n      obj.tokens = answer.prompt_token_size;\n\n      let incremented_key = quoteManager.incrementTokenCount(req.project, obj);\n      winston.verbose(\"incremented_key: \", incremented_key);\n    }\n  \n    return res.status(200).send(answer);\n\n  }).catch((err) => {\n    winston.error(\"qa err: \", err);\n    winston.error(\"qa err.response: \", err.response);\n    if (err.response && err.response.status) {\n      let status = err.response.status;\n      res.status(status).send({ success: false, statusText: err.response.statusText, error: err.response.data.detail });\n    }\n    else {\n      res.status(500).send({ success: false, error: err });\n    }\n\n  })\n\n})\n\n// router.post('/qa', async (req, res) => {\n\n//   let project_id = req.projectid;\n//   let publicKey = false;\n//   let data = req.body;\n//   let ollama_integration;\n//   let vllm_integration;\n\n//   let namespace;\n//   try {\n//     namespace = await aiManager.checkNamespace(project_id, data.namespace);\n//   } catch (err) {\n//     let errorCode = err?.errorCode ?? 500;\n//     return res.status(errorCode).send({ success: false, error: err.error });\n//   }\n//   winston.debug(\"/qa data: \", data);\n\n//   if (!data.llm) {\n//     data.llm = \"openai\";\n//   }\n\n//   if (data.llm === 'ollama') {\n//     data.gptkey = process.env.GPTKEY;\n//     try {\n//       ollama_integration = await integrationService.getIntegration(project_id, 'ollama');\n//     } catch (err) {\n//       let error_code = err.code || 500;\n//       let error_message = err.error || `Unable to get integration for ${data.llm}`;\n//       return res.status(error_code).send({ success: false, error: error_message });\n//     }\n//   }\n//   else if (data.llm === 'vllm') {\n//     data.gptkey = process.env.GPTKEY;\n//     try {\n//       vllm_integration = await integrationService.getIntegration(project_id, 'vllm')\n//     } catch (err) {\n//       let error_code = err.code || 500;\n//       let error_message = err.error || `Unable to get integration for ${data.llm}`;\n//       return res.status(error_code).send({ success: false, error: error_message });\n//     }\n//   } else {\n//     try {\n//       let key = await integrationService.getKeyFromIntegration(project_id, data.llm);\n\n//       if (!key) {\n//         if (data.llm === 'openai') {\n//           data.gptkey = process.env.GPTKEY;\n//           publicKey = true;\n//         } else {\n//           return res.status(404).send({ success: false, error: `Invalid or empty key provided for ${data.llm}` });\n//         }\n//       } else {\n//         data.gptkey = key;\n//       }\n\n//     } catch (err) {\n//       let error_code = err.code || 500;\n//       let error_message = err.error || `Unable to get integration for ${data.llm}`;\n//       return res.status(error_code).send({ success: false, error: error_message });\n//     }\n//   }\n\n//   let obj = { createdAt: new Date() };\n\n//   let quoteManager = req.app.get('quote_manager');\n//   if (publicKey === true) {\n//     let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');\n//     if (isAvailable === false) {\n//       return res.status(403).send({ success: false, message: \"Tokens quota exceeded\", error_code: 13001})\n//     }\n//   }\n\n//   // Check if \"Advanced Mode\" is active. In such case the default_context must be not appended\n//   if (!data.advancedPrompt) {\n//     const contextTemplate = contexts[data.model] || contexts[\"general\"];\n//     if (data.system_context) {\n//       data.system_context = data.system_context + \" \\n\" + contextTemplate;\n//     } else {\n//       data.system_context = contextTemplate;\n//     }\n//   }\n\n//   data.engine = namespace.engine || default_engine;\n//   data.embedding = namespace.embedding || default_embedding;\n//   data.embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n\n//   if (namespace.hybrid === true) {\n//     data.search_type = 'hybrid';\n    \n//     if (data.reranking === true) {\n//       data.reranking_multiplier = 3;\n//       data.reranker_model = \"cross-encoder/ms-marco-MiniLM-L-6-v2\";\n//     }\n//   }\n\n//   if (data.llm === 'ollama') {\n//     if (!ollama_integration.value.url) {\n//       return res.status(422).send({ success: false, error: \"Server url for ollama is empty or invalid\"})\n//     }\n//     data.model = {\n//       name: data.model,\n//       url: ollama_integration.value.url,\n//       provider: 'ollama'\n//     }\n//     data.stream = false;\n//   }\n\n//   if (data.llm === 'vllm') {\n//     if (!vllm_integration.value.url) {\n//       return res.status(422).send({ success: false, error: \"Server url for vllm is empty or invalid\"})\n//     }\n//     data.model = {\n//       name: data.model,\n//       url: vllm_integration.value.url,\n//       provider: 'vllm'\n//     }\n//     data.stream = false;\n//   }\n\n//   delete data.advancedPrompt;\n//   winston.verbose(\"ask data: \", data);\n  \n//   if (process.env.NODE_ENV === 'test') {\n//     return res.status(200).send({ success: true, message: \"Question skipped in test environment\", data: data });\n//   }\n\n//   data.debug = true;\n\n//   aiService.askNamespace(data).then((resp) => {\n//     winston.debug(\"qa resp: \", resp.data);\n//     let answer = resp.data;\n\n//     if (publicKey === true) {\n//       let multiplier = MODELS_MULTIPLIER[data.model];\n//       if (!multiplier) {\n//         multiplier = 1;\n//         winston.info(\"No multiplier found for AI model\")\n//       }\n//       obj.multiplier = multiplier;\n//       obj.tokens = answer.prompt_token_size;\n\n//       let incremented_key = quoteManager.incrementTokenCount(req.project, obj);\n//       winston.verbose(\"incremented_key: \", incremented_key);\n//     }\n  \n//     return res.status(200).send(answer);\n\n//   }).catch((err) => {\n//     winston.error(\"qa err: \", err);\n//     winston.error(\"qa err.response: \", err.response);\n//     if (err.response && err.response.status) {\n//       let status = err.response.status;\n//       res.status(status).send({ success: false, statusText: err.response.statusText, error: err.response.data.detail });\n//     }\n//     else {\n//       res.status(500).send({ success: false, error: err });\n//     }\n\n//   })\n// })\n\nrouter.delete('/delete', async (req, res) => {\n\n  let project_id = req.projectid;\n  let data = req.body;\n  winston.debug(\"/delete data: \", data);\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  data.engine = namespace.engine || default_engine;\n\n  aiService.deleteIndex(data).then((resp) => {\n    winston.debug(\"delete resp: \", resp.data);\n    res.status(200).send(resp.data);\n  }).catch((err) => {\n    winston.error(\"delete err: \", err);\n    let status = err.response.status;\n    res.status(status).send({ statusText: err.response.statusText, error: err.response.data.detail });\n  })\n\n})\n\nrouter.delete('/deleteall', async (req, res) => {\n\n  let project_id = req.projectid;\n  let data = req.body;\n  winston.debug('/delete all data: ', data);\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  data.engine = namespace.engine || default_engine;\n\n  winston.verbose(\"/deleteall data: \", data);\n\n  aiService.deleteNamespace(data).then((resp) => {\n    winston.debug(\"delete namespace resp: \", resp.data);\n    res.status(200).send(resp.data);\n  }).catch((err) => {\n    winston.error(\"delete namespace err: \", err);\n    let status = err.response.status;\n    res.status(status).send({ statusText: err.response.statusText, error: err.response.data.detail });\n  })\n})\n/**\n* ****************************************\n* Proxy Section - Start\n* ****************************************\n*/\n\n\n//----------------------------------------\n\n\n/**\n * ****************************************\n * Namespace Section - Start\n * ****************************************\n */\nrouter.get('/namespace/all', async (req, res) => {\n\n  let project_id = req.projectid;\n\n  Namespace.find({ id_project: project_id }).lean().exec( async (err, namespaces) => {\n\n    if (err) {\n      winston.error(\"find namespaces error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n    else if (namespaces.length == 0) {\n\n      // Create Default Namespace\n      let new_namespace = new Namespace({\n        id_project: project_id,\n        id: project_id,\n        name: \"Default\",\n        preview_settings: default_preview_settings,\n        default: true,\n        engine: default_engine,\n        embedding: default_embedding\n      })\n\n      new_namespace.save((err, savedNamespace) => {\n        if (err) {\n          winston.error(\"create default namespace error: \", err);\n          return res.status(500).send({ success: false, error: err });\n        }\n\n        let namespaceObj = savedNamespace.toObject();\n        delete namespaceObj._id;\n        delete namespaceObj.__v;\n\n        let namespaces = [];\n        namespaces.push(namespaceObj);\n\n        return res.status(200).send(namespaces);\n\n      })\n\n    } else {\n\n      let namespaceObjArray = [];\n      if (req.query.count) {\n        namespaceObjArray = await Promise.all(\n          namespaces.map(async ({ _id, __v, ...keepAttrs }) => {\n            const count = await KB.countDocuments({ id_project: keepAttrs.id_project, namespace: keepAttrs.id });\n            return { ...keepAttrs, count };\n          })\n        );\n      } else {\n        namespaceObjArray = namespaces.map(({ _id, __v, ...keepAttrs }) => keepAttrs)\n      }\n        \n      winston.debug(\"namespaceObjArray: \", namespaceObjArray);\n      return res.status(200).send(namespaceObjArray);\n    }\n  })\n})\n\nrouter.get('/namespace/:id/chunks/:content_id', async (req, res) => {\n\n  let project_id = req.projectid;\n  let namespace_id = req.params.id;\n  let content_id = req.params.content_id;\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  let content = await KB.find({ id_project: project_id, namespace: namespace_id, _id: content_id }).catch((err) => {\n    winston.error(\"find content error: \", err);\n    return res.status(403).send({ success: false, error: err })\n  })\n\n  if(!content) {\n    return res.status(403).send({ success: false, error: \"Not allowed. The content does not belong to the current namespace.\" })\n  }\n\n  let engine = namespace.engine || default_engine;\n\n  if (process.env.NODE_ENV === 'test') {\n    return res.status(200).send({ success: true, message: \"Get chunks skipped in test environment\"});\n  }\n\n  aiService.getContentChunks(namespace_id, content_id, engine).then((resp) => {\n    let chunks = resp.data;\n    winston.debug(\"chunks for content \" + content_id);\n    winston.debug(\"chunks found \", chunks);\n    return res.status(200).send(chunks);\n\n  }).catch((err) => {\n    winston.error(\"error getting content chunks err.response: \", err.response)\n    winston.error(\"error getting content chunks err.data: \", err.data)\n    return res.status(500).send({ success: false, error: err });\n  })\n\n})\n\nrouter.get('/namespace/:id/chatbots', async (req, res) => {\n\n  let project_id = req.projectid;\n  let namespace_id = req.params.id;\n  \n  let chatbotsArray = [];\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  let intents = await faq.find({ id_project: project_id, 'actions.namespace': namespace_id }).catch((err) => {\n    winston.error(\"Error find intents: \", err);\n    return res.status(500).send({ success: false, message: 'Unable to retrieve intents using the selected namespace', error: err });\n  })\n\n  if (!intents || intents.length == 0) {\n    winston.verbose(\"No intents found for the selected chatbot\")\n    return res.status(200).send(chatbotsArray);\n  }\n\n  let chatbots = intents.map(i => i.id_faq_kb);\n  let uniqueChatbots = [...new Set(chatbots)];\n\n  let chatbotPromises = uniqueChatbots.map(async (c_id) => {\n    try {\n      let chatbot = await faq_kb.findOne({ _id: c_id, trashed: false });\n      if (chatbot) {\n        let data = {\n          _id: chatbot._id,\n          name: chatbot.name\n        }\n        chatbotsArray.push(data);\n      }\n    } catch (err) {\n      winston.error(\"error getting chatbot: \", err);\n    }\n  });\n\n  await Promise.all(chatbotPromises);\n\n  winston.debug(\"chatbotsArray: \", chatbotsArray);\n  \n  res.status(200).send(chatbotsArray);\n})\n\nrouter.get('/namespace/export/:id', async (req, res) => {\n  \n  let id_project = req.projectid;\n  let namespace_id = req.params.id;\n\n  let query = {};\n  query.id_project = id_project;\n  query.namespace = namespace_id;\n\n  if (req.query.status) {\n    query.status = parseInt(req.query.status)\n  }\n\n  query.type = { $in: [ kbTypes.URL, kbTypes.TEXT, kbTypes.FAQ ] };\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, namespace_id);\n  } catch (err) {\n    winston.error(\"Error checking namespace: \", err);\n    let errorCode = err?.errorCode ?? 500;\n    let errorMessage = err?.error || \"Error checking namespace\";\n    return res.status(errorCode).send({ success: false, error: errorMessage });\n  }\n\n  let name = namespace.name;\n  let preview_settings = namespace.preview_settings;\n\n  let contents = await KB.find(query).catch((err) => {\n    winston.error(\"Error getting contents for export \", err);\n    return res.status(500).send({ success: false, error: \"Unable to get contents for namespace \" + namespace_id })\n  })\n\n  try {\n    let filename = await aiManager.generateFilename(name);\n    let json = {\n      name: name,\n      preview_settings: preview_settings,\n      contents: contents\n    }\n    let json_string = JSON.stringify(json);\n    res.set({ \"Content-Disposition\": `attachment; filename=\"${filename}.json\"` });\n    return res.send(json_string);\n  } catch(err) {\n    winston.error(\"Error genereting json \", err);\n    return res.status(500).send({ success: false, error: \"Error genereting json file\" })\n  }\n  \n})\n\nrouter.post('/namespace', async (req, res) => {\n\n  let project_id = req.projectid;\n  let body = req.body;\n  winston.debug(\"add namespace body: \", body);\n\n  let engine = default_engine;\n\n  let hybrid = false;\n  if ('hybrid' in req.body) {\n    if (typeof req.body.hybrid !== 'boolean') {\n      return res.status(400).send({ success: false, error: \"Value not accepted for 'hybrid' field. Expected boolean.\" });\n    }\n    hybrid = req.body.hybrid;\n  }\n\n  if (hybrid) {\n    if (req.project?.profile?.customization?.hybrid) {\n      engine = default_engine_hybrid;\n    } else {\n      return res.status(403).send({ success: false, error: \"Hybrid mode is not allowed for the current project\" });\n    }\n  }\n\n  var namespace_id = mongoose.Types.ObjectId();\n  let new_namespace = new Namespace({\n    id_project: project_id,\n    id: namespace_id,\n    name: body.name,\n    hybrid: hybrid,\n    preview_settings: default_preview_settings,\n    engine: engine,\n    embedding: default_embedding\n  })\n\n  let namespaces = await Namespace.find({ id_project: project_id }).catch((err) => {\n    winston.error(\"find namespaces error: \", err)\n    //res.status(500).send({ success: false, error: err })\n  })\n  if (!namespaces || namespaces.length == 0) {\n    new_namespace.default = true;\n  }\n\n  let quoteManager = req.app.get('quote_manager');\n  let limits = await quoteManager.getPlanLimits(req.project);\n  let ns_limit = limits.namespace;\n\n  if (namespaces.length >= ns_limit) {\n    return res.status(403).send({ success: false, error: \"Maximum number of resources reached for the current plan\", plan_limit: ns_limit });\n  }\n\n  new_namespace.save((err, savedNamespace) => {\n    if (err) {\n      winston.error(\"create namespace error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    let namespaceObj = savedNamespace.toObject();\n    delete namespaceObj._id;\n    delete namespaceObj.__v;\n    return res.status(200).send(namespaceObj);\n  })\n})\n\nrouter.post('/namespace/import/:id', upload.single('uploadFile'), async (req, res) => {\n\n  let id_project = req.projectid;\n  let namespace_id = req.params.id;\n\n  let json_string;\n  let json;\n  if (req.file) {\n    json_string = req.file.buffer.toString('utf-8');\n    json = JSON.parse(json_string);\n  } else {\n    json = req.body;\n  }\n\n  if (!json.contents) {\n    winston.warn(\"Imported json don't contain contents array\");\n    return res.status(400).send({ success: false, error: \"Imported json must contain the contents array\" });\n  }\n\n  if (!Array.isArray(json.contents)) {\n    winston.warn(\"Invalid contents type. Expected type: array\");\n    return res.status(400).send({ success: false, error: \"The content field must be of type Array[]\" });\n  }\n\n  let contents = json.contents;\n\n  let namespaces = await Namespace.find({ id_project: id_project }).catch((err) => {\n    winston.error(\"find namespaces error: \", err)\n    res.status(500).send({ success: false, error: err })\n  })\n\n  if (!namespaces || namespaces.length == 0) {\n    let alert = \"No namespace found for the selected project \" + id_project + \". Cannot add content to a non-existent namespace.\"\n    winston.warn(alert);\n    res.status(403).send(alert);\n  }\n\n  let namespaceIds = namespaces.map(namespace => namespace.id);\n\n  if (!namespaceIds.includes(namespace_id)) {\n    return res.status(403).send({ success: false, error: \"Not allowed. The namespace does not belong to the current project.\" })\n  }\n\n  let quoteManager = req.app.get('quote_manager');\n  let limits = await quoteManager.getPlanLimits(req.project);\n  let kbs_limit = limits.kbs;\n  winston.verbose(\"Limit of kbs for current plan: \" + kbs_limit);\n\n  // let kbs_count = await KB.countDocuments({ id_project: id_project }).exec();\n  // winston.verbose(\"Kbs count: \" + kbs_count);\n\n  // if (kbs_count >= kbs_limit) {\n  //   return res.status(403).send({ success: false, error: \"Maximum number of resources reached for the current plan\", plan_limit: kbs_limit })\n  // }\n\n  // let total_count = kbs_count + contents.length;\n  if (contents.length > kbs_limit) {\n    return res.status(403).send({ success: false, error: \"Cannot exceed the number of resources in the current plan\", plan_limit: kbs_limit })\n  }\n\n  let addingContents = [];\n  contents.forEach( async (e) => {\n    let content = {\n      id_project: id_project,\n      name: e.name,\n      source: e.source,\n      type: e.type,\n      content: e.content,\n      namespace: namespace_id,\n      status: -1,\n      ...(e.tags && { tags: e.tags })\n    }\n\n    const optionalFields = ['scrape_type', 'scrape_options', 'refresh_rate'];\n\n    for (const key of optionalFields) {\n      if (e[key] !== undefined) {\n        content[key] = e[key];\n      }\n    }\n\n    addingContents.push(content);\n  })\n\n  // const operations = addingContents.map(({ _id, ...doc }) => ({\n  //   replaceOne: {\n  //     filter: {\n  //       id_project: doc.id_project,\n  //       type: doc.type,\n  //       source: doc.source,\n  //       namespace: namespace_id\n  //     },\n  //     replacement: doc,\n  //     upsert: true\n  //   }\n  // }));\n\n  // Try without delete all contents before imports\n  // Issue 1: the indexes of the content replaced are not deleted from Pinecone\n  // Issue 2: isn't possibile to know how many content will be replaced, so isn't possibile to determine if after the\n  //          import operation the content's limit is respected\n  let ns = namespaces.find(n => n.id === namespace_id);\n  let engine = ns.engine || default_engine;\n  let embedding = normalizeEmbedding(ns.embedding);\n  embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n  let hybrid = ns.hybrid;\n  const situated_context = normalizeSituatedContext();\n\n\n  if (process.env.NODE_ENV !== \"test\") {\n    await aiService.deleteNamespace({\n      namespace: namespace_id,\n      engine: engine\n    }).catch((err) => {\n      winston.error(\"Error deleting namespace contents: \", err)\n      return res.status(500).send({ success: true, error: \"Unable to delete namespace contents\" })\n    });\n  }\n\n  let deleteResponse = await KB.deleteMany({ id_project: id_project, namespace: namespace_id, type: { $in: ['url', 'text', 'faq'] } }).catch((err) => {\n    winston.error(\"deleteMany error: \", err);\n    return res.status(500).send({ success: false, error: err });\n  })\n\n  winston.verbose(\"Content deletetion response: \", deleteResponse);\n\n  try {\n    let addedContents = await KB.insertMany(addingContents);\n    winston.debug(\"addedContents: \", addedContents);\n  } catch (err) {\n    winston.error(\"Error adding contents with insertMany: \", err);\n    return res.status(500).send({ success: true, error: \"Error importing contents\" });\n  }\n\n  let new_contents;\n  try {\n    new_contents = await KB.find({ id_project: id_project, namespace: namespace_id }).lean();\n  } catch (err) {\n    winston.error(\"Error getting new contents: \", err);\n    return res.status(500).send({ success: false, error: \"Unable to get new content\" });\n  }\n\n  let resources = new_contents.map(({ name, status, __v, createdAt, updatedAt, id_project, ...keepAttrs }) => keepAttrs)\n  resources = resources.map(({ _id, scrape_options, ...rest }) => {\n    return { \n      id: _id, \n      parameters_scrape_type_4: scrape_options, \n      embedding: embedding, \n      engine: engine, \n      ...(situated_context && { situated_context: situated_context }), \n      ...rest}\n  });\n  \n  winston.verbose(\"resources to be sent to worker: \", resources);\n\n  if (process.env.NODE_ENV !== \"test\") {\n    aiManager.scheduleScrape(resources, hybrid);\n  }\n\n  res.status(200).send({ success: true, message: \"Contents imported successfully\" });\n\n\n  // saveBulk(operations, addingContents, id_project).then((result) => {\n    \n  //   let ns = namespaces.find(n => n.id === namespace_id);\n  //   let engine = ns.engine || default_engine;\n\n  //   let resources = result.map(({ name, status, __v, createdAt, updatedAt, id_project, ...keepAttrs }) => keepAttrs)\n  //   resources = resources.map(({ _id, scrape_options, ...rest }) => {\n  //     return { id: _id, parameters_scrape_type_4: scrape_options, engine: engine, ...rest}\n  //   });\n\n  //   winston.verbose(\"resources to be sent to worker: \", resources);\n\n  //   if (process.env.NODE_ENV !== 'test') {\n  //     scheduleScrape(resources);\n  //   }\n    \n  //   //res.status(200).send(result);\n  //   res.status(200).send({ success: true, message: \"Contents imported successfully\"});\n\n  // }).catch((err) => {\n  //   winston.error(\"Unable to save kbs in bulk \", err)\n  //   res.status(500).send(err);\n  // })\n  \n})\n\nrouter.put('/namespace/:id', async (req, res) => {\n\n  let namespace_id = req.params.id;\n  let body = req.body;\n  winston.debug(\"update namespace body: \", body);\n\n  let update = {};\n\n  if (body.name) {\n    update.name = body.name;\n  }\n  if (body.preview_settings) {\n    update.preview_settings = body.preview_settings;\n  }\n\n  Namespace.findOneAndUpdate({ id: namespace_id }, update, { new: true, upsert: true }, (err, updatedNamespace) => {\n    if (err) {\n      winston.error(\"update namespace error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    let namespaceObj = updatedNamespace.toObject();\n    delete namespaceObj._id;\n    delete namespaceObj.__v;\n    res.status(200).send(namespaceObj);\n  })\n})\n\nrouter.delete('/namespace/:id', async (req, res) => {\n\n  let project_id = req.projectid;\n  let namespace_id = req.params.id;\n\n  let namespace = await Namespace.findOne({ id_project: project_id, id: namespace_id }).catch((err) => {\n    winston.error(\"find namespace error: \", err);\n    res.status(500).send({ success: false, error: err });\n  })\n\n  if (!namespace) {\n    let alert = \"No namespace found for id \" + namespace_id;\n    winston.warn(alert);\n    res.status(404).send({ success: false, error: alert })\n  }\n\n  let data = {\n    namespace: namespace_id,\n    engine: namespace.engine || default_engine\n  }\n\n  if (req.query.contents_only && (req.query.contents_only === true || req.query.contents_only === 'true')) {\n\n    aiService.deleteNamespace(data).then(async (resp) => {\n      winston.debug(\"delete namespace resp: \", resp.data);\n\n      let deleteResponse = await KB.deleteMany({ id_project: project_id, namespace: namespace_id }).catch((err) => {\n        winston.error(\"deleteMany error: \", err);\n        return res.status(500).send({ success: false, error: err });\n      })\n      winston.debug(\"delete all contents response: \", deleteResponse);\n\n      return res.status(200).send({ success: true, message: \"All contents deleted successfully\" })\n\n    }).catch((err) => {\n\n      winston.error(\"deleteNamespace err: \", err);\n      winston.error(\"deleteNamespace err.response: \", err.response);\n      if (err.response && err.response.status) {\n        let status = err.response.status;\n        res.status(status).send({ success: false, statusText: err.response.statusText, error: err.response.data.detail });\n      } else {\n        res.status(500).send({ success: false, message: \"Unable to delete namespace\", error: err })\n      }\n    })\n\n  } else {\n\n    let namespace = await Namespace.findOne({ id: namespace_id }).catch((err) => {\n      winston.error(\"findOne namespace error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    })\n    if (namespace.default === true) {\n      winston.error(\"Default namespace cannot be deleted\");\n      return res.status(403).send({ success: false, error: \"Default namespace cannot be deleted\" });\n    }\n\n    aiService.deleteNamespace(data).then(async (resp) => {\n      winston.debug(\"delete namespace resp: \", resp.data);\n\n      let deleteResponse = await KB.deleteMany({ id_project: project_id, namespace: namespace_id }).catch((err) => {\n        winston.error(\"deleteMany error: \", err);\n        return res.status(500).send({ success: false, error: err });\n      })\n      winston.debug(\"delete all contents response: \", deleteResponse);\n\n      let deleteNamespaceResponse = await Namespace.findOneAndDelete({ id: namespace_id }).catch((err) => {\n        winston.error(\"deleteOne namespace error: \", err);\n        return res.status(500).send({ success: false, error: err });\n      })\n      winston.debug(\"delete namespace response: \", deleteNamespaceResponse);\n\n      return res.status(200).send({ success: true, message: \"Namespace deleted succesfully\" })\n\n    }).catch((err) => {\n      let status = 400;\n      if (err.response && err.response.status) {\n        status = err.response.status;\n      }\n      return res.status(status).send({ success: false, error: \"Unable to delete namespace\" })\n    })\n\n  }\n\n\n\n})\n/**\n * ****************************************\n * Namespace Section - End\n * ****************************************\n */\n\n\n//----------------------------------------\n\n\n/**\n* ****************************************\n* Content Section - Start\n* ****************************************\n*/\nrouter.get('/', async (req, res) => {\n\n  let project_id = req.projectid;\n  let namespace = req.query.namespace;\n  if (!namespace) {\n    return res.status(400).send({ success: false, error: \"queryParam 'namespace' is not defined\" })\n  }\n  let status;\n  let type;\n  let limit = 200;\n  let page = 0;\n  let direction = -1;\n  let sortField = \"updatedAt\";\n  let text;\n\n  let query = {};\n  query[\"id_project\"] = project_id;\n  query[\"namespace\"] = namespace;\n\n  if (req.query.status) {\n    status = parseInt(req.query.status);\n    query[\"status\"] = status;\n    winston.debug(\"Get kb status: \" + status)\n  }\n\n  if (req.query.type) {\n    type = req.query.type;\n    query[\"type\"] = type;\n    winston.debug(\"Get kb type: \" + type);\n  }\n\n  if (req.query.limit) {\n    limit = parseInt(req.query.limit);\n    winston.debug(\"Get kb limit: \" + limit)\n  }\n\n  if (req.query.page) {\n    page = parseInt(req.query.page);\n    winston.debug(\"Get kb page: \" + page)\n  }\n\n  let skip = page * limit;\n  winston.debug(\"Get kb skip page: \" + skip);\n\n  if (req.query.direction) {\n    direction = parseInt(req.query.direction)\n    winston.debug(\"Get kb direction: \" + direction)\n  }\n\n  if (req.query.sortField) {\n    sortField = req.query.sortField;\n    winston.debug(\"Get kb sortField: \" + sortField)\n  }\n\n  if (req.query.search) {\n    text = req.query.search;\n    query['source'] = new RegExp(text);\n    winston.debug(\"Get kb text: \" + text);\n  }\n\n  let sortQuery = {};\n  sortQuery[sortField] = direction;\n  winston.debug(\"Get kb sortQuery: \" + sortQuery);\n\n  KB.countDocuments(query, (err, kbs_count) => {\n    if (err) {\n      winston.error(\"Find all kbs error: \", err);\n    }\n    winston.debug(\"KBs count: \", kbs_count);\n\n    KB.find(query)\n      .skip(skip)\n      .limit(limit)\n      .sort(sortQuery)\n      .exec((err, kbs) => {\n        if (err) {\n          winston.error(\"Find all kbs error: \", err);\n          return res.status(500).send({ success: false, error: err });\n        }\n\n        winston.debug(\"KBs found: \", kbs);\n\n        let response = {\n          count: kbs_count,\n          query: {},\n          kbs: kbs\n        }\n        if (status) {\n          response.query.status = status;\n        }\n        if (limit) {\n          response.query.limit = limit;\n        }\n        if (status) {\n          response.query.page = page;\n        }\n        if (sortField) {\n          response.query.sortField = sortField;\n        }\n        if (direction) {\n          response.query.direction = direction;\n        }\n        if (text) {\n          response.query.search = text;\n        }\n\n\n        return res.status(200).send(response);\n      })\n\n  })\n\n\n})\n\nrouter.get('/:kb_id', async (req, res) => {\n\n  let kb_id = req.params.kb_id;\n\n  KB.findById(kb_id, (err, kb) => {\n    if (err) {\n      winston.error(\"Find kb by id error: \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    if (!kb) {\n      return res.status(404).send({ success: false, error: \"Content not found with id \" + kb_id });\n    }\n\n    return res.status(200).send(kb);\n  })\n})\n\nrouter.post('/', async (req, res) => {\n\n  const id_project = req.projectid;\n  const project = req.project\n\n  const { name, type, source, content, refresh_rate, scrape_type, scrape_options, tags } = req.body;\n  const namespace_id = req.body?.namespace;\n  \n  if (!namespace_id) {\n    return res.status(400).send({ success: false, error: \"parameter 'namespace' is not defined\" });\n  }\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n  \n  const quoteManager = req.app.get('quote_manager');\n  try {\n    await aiManager.checkQuotaAvailability(quoteManager, project, 1)\n  } catch(err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error, plan_limit: err.plan_limit })\n  }\n\n  let new_kb = {\n    id_project,\n    name,\n    type,\n    source,\n    content,\n    namespace: namespace_id,\n    status: -1\n  }\n\n  if (type === 'txt') {\n    new_kb.scrape_type = 1;\n  }\n  if (type === 'url') {\n    new_kb.refresh_rate = refresh_rate || 'never';\n    if (scrape_type === 0 || scrape_type === 4) {\n      new_kb.scrape_type = scrape_type;\n      new_kb.scrape_options = scrape_options;\n    }\n    else {\n      new_kb.scrape_type = 2;\n      new_kb.scrape_options = aiManager.setDefaultScrapeOptions();\n    }\n  }\n\n  if (tags && Array.isArray(tags) && tags.every(tag => typeof tag === \"string\")) {\n    new_kb.tags = tags;\n  }\n\n  winston.debug(\"adding kb: \", new_kb);\n\n  KB.findOneAndUpdate({ id_project, type, source }, new_kb, { upsert: true, new: true, rawResult: true }, async (err, raw_content) => {\n    if (err) {\n      winston.error(\"findOneAndUpdate with upsert error: \", err);\n      res.status(500).send({ success: false, error: err });\n    }\n    else {\n\n      delete raw_content.ok;\n      delete raw_content.$clusterTime;\n      delete raw_content.operationTime;\n      \n      const saved_kb = raw_content.value;\n      const webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;\n      const embedding = normalizeEmbedding(namespace.embedding);\n      embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n\n      const situated_context = normalizeSituatedContext();\n\n      const json = {\n        id: saved_kb._id,\n        type: saved_kb.type,\n        source: saved_kb.source,\n        content: saved_kb.content || \"\",\n        namespace: saved_kb.namespace,\n        webhook: webhook,\n        hybrid: namespace.hybrid,\n        engine: namespace.engine || default_engine,\n        embedding: embedding,\n        ...(situated_context && { situated_context: situated_context }),\n        ...(saved_kb.scrape_type && { scrape_type: saved_kb.scrape_type }),\n        ...(saved_kb.scrape_options && { parameters_scrape_type_4: saved_kb.scrape_options }),\n        ...(saved_kb.tags && { tags: saved_kb.tags }),\n      }\n\n      winston.debug(\"json: \", json);\n\n      if (process.env.NODE_ENV === 'test') {\n        return res.status(200).send({ success: true, message: \"Schedule scrape skipped in test environment\", data: raw_content, schedule_json: json });\n      }\n        \n      aiManager.scheduleScrape([json], namespace.hybrid);\n      return res.status(200).send(raw_content);\n\n    }\n  })\n\n})\n\nrouter.post('/multi', upload.single('uploadFile'), async (req, res) => {\n\n  let list;\n  if (req.file) {\n    file_string = req.file.buffer.toString('utf-8');\n    list = file_string.trim().split('\\n');\n  } else {\n    list = req.body.list;\n  }\n\n  const id_project = req.projectid;\n  const project = req.project;\n  let { refresh_rate = 'never', scrape_type = 2, scrape_options } = req.body;\n  let tags = parseStringArrayField(req.body.tags);\n\n\n  if (scrape_type === 2 && !scrape_options) {\n    try {\n      scrape_options = aiManager.setDefaultScrapeOptions();\n    } catch (err) {\n      winston.error(\"Error setting default scrape options: \", err);\n      return res.status(500).send({ success: false, error: err.error });\n    }\n  }\n\n  const namespace_id = req.query.namespace;\n  if (!namespace_id) {\n    return res.status(400).send({ success: false, error: \"queryParam 'namespace' is not defined\" })\n  }\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, namespace_id);\n  } catch (err) {\n    winston.error(\"Error checking namespace: \", err);\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  const quoteManager = req.app.get('quote_manager');\n  try {\n    await aiManager.checkQuotaAvailability(quoteManager, project, list.length)\n  } catch(err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error, plan_limit: err.plan_limit })\n  }\n\n  if (list.length > 300) {\n    winston.error(\"Too many urls. Can't index more than 300 urls at a time.\");\n    return res.status(403).send({ success: false, error: \"Too many urls. Can't index more than 300 urls at a time.\" })\n  }\n\n  const options = {\n    scrape_type,\n    scrape_options,\n    refresh_rate,\n    ...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})\n  }\n\n  try {\n    const result = await aiManager.addMultipleUrls(namespace, list, options);\n    return res.status(200).send(result);\n  } catch (err) {\n    winston.error(\"addMultipleUrls error: \", err)\n    return res.status(500).send({ success: false, error: \"Unable to add multiple urls due to an error.\" });\n  }\n\n})\n\nrouter.post('/csv', upload.single('uploadFile'), async (req, res) => {\n\n  let project_id = req.projectid;\n  let namespace_id = req.query.namespace;\n\n  let csv = req.file.buffer.toString('utf8');\n  winston.debug(\"csv: \", csv);\n\n  const { delimiter = ';' } = req.body;\n  let tags = parseStringArrayField(req.body.tags);\n\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  let webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;\n\n  let kbs = [];\n\n  parsecsv.parseString(csv, { headers: false, delimiter: delimiter })\n    .on(\"data\", (data) => {\n\n      let question = data[0];\n      let answer = data[1];\n\n      kbs.push({\n        id_project: project_id,\n        name: question,\n        source: question,\n        type: 'faq',\n        content: question + \"\\n\" + answer,\n        namespace: namespace_id,\n        status: -1,\n        ...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})\n      })\n    })\n    .on(\"end\", async () => {\n      winston.debug(\"kbs after CSV parsing: \", kbs);\n\n      const quoteManager = req.app.get('quote_manager');\n      try {\n        await aiManager.checkQuotaAvailability(quoteManager, req.project, kbs.length)\n      } catch(err) {\n        let errorCode = err?.errorCode ?? 500;\n        return res.status(errorCode).send({ success: false, error: err.error, plan_limit: err.plan_limit })\n      }\n\n      let operations = kbs.map(doc => {\n        return {\n          updateOne: {\n            filter: { id_project: doc.id_project, type: 'faq', source: doc.source, namespace: namespace_id },\n            update: doc,\n            upsert: true,\n            returnOriginal: false\n          }\n        }\n      })\n\n      aiManager.saveBulk(operations, kbs, project_id, namespace_id).then((result) => {\n\n        let engine = namespace.engine || default_engine;\n        let embedding = normalizeEmbedding(namespace.embedding);\n        embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n        let hybrid = namespace.hybrid;\n        const situated_context = normalizeSituatedContext();\n\n        let resources = result.map(({ name, status, __v, createdAt, updatedAt, id_project,  ...keepAttrs }) => keepAttrs)\n        resources = resources.map(({ _id, ...rest}) => {\n          return { \n            id: _id, \n            webhook: webhook, \n            embedding: embedding, \n            engine: engine, \n            ...(situated_context && { situated_context: situated_context }), \n            ...rest \n          };\n        })\n        winston.verbose(\"resources to be sent to worker: \", resources);\n\n        if (process.env.NODE_ENV === 'test') {\n          return res.status(200).send({ success: true, message: \"Schedule scrape skipped in test environment\", data: result, schedule_json: resources });\n        }\n        \n        aiManager.scheduleScrape(resources, hybrid);\n        return res.status(200).send(result);\n\n      }).catch((err) => {\n        winston.error(\"Unabled to saved kbs in bulk \" + err);\n        res.status(500).send(err);\n      })\n\n    })\n    .on(\"error\", (err) => {\n      winston.error(\"CSV parsing error: \", err);\n      res.status(400).send({ success: false, error: err });\n    })\n\n})\n\nrouter.post('/sitemap', async (req, res) => {\n\n  let sitemap_url = req.body.sitemap;\n\n  const sitemap = new Sitemapper({\n    url: sitemap_url,\n    timeout: 15000,\n    debug: false\n  });\n\n  sitemap.fetch().then((data) => {\n    // TODO - check on data.errors to catch error\n    winston.debug(\"data: \", data);\n    res.status(200).send(data);\n  }).catch((err) => {\n    console.error(\"err \", err)\n    res.status(500).send({ success: false, error: err });\n  })\n\n})\n\nrouter.post('/sitemap/import', async (req, res) => {\n\n  const id_project = req.projectid;\n  const namespace_id = req.query.namespace;\n\n  let { type, source, refresh_rate = 'never', scrape_type = 2, scrape_options, tags } = req.body;\n  if (scrape_type === 2 && !scrape_options) {\n    scrape_options = aiManager.setDefaultScrapeOptions();\n  }\n\n  if (type !== \"sitemap\") {\n    return res.status(403).send({success: false, error: \"Endpoint available for sitemap type only.\" });\n  }\n\n  if (!namespace_id) {\n    return res.status(400).send({ success: false, error: \"queryParam 'namespace' is not defined\" })\n  }\n  \n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n\n  // let quoteManager = req.app.get('quote_manager');\n  // let limits = await quoteManager.getPlanLimits(req.project);\n  // let kbs_limit = limits.kbs;\n  // winston.verbose(\"Limit of kbs for current plan: \" + kbs_limit);\n\n  // let kbs_count = await KB.countDocuments({ id_project: project_id }).exec();\n  // winston.verbose(\"Kbs count: \" + kbs_count);\n\n  const sitemap = new Sitemapper({\n    url: source,\n    timeout: 15000,\n    debug: false\n  });\n\n  const data = await sitemap.fetch().catch((err) => {\n    winston.error(\"Error fetching sitemap: \", err);\n    return res.status(500).send({ success: false, error: err });\n  })\n\n  if (data.errors && data.errors.length > 0) {\n    winston.error(\"An error occurred during sitemap fetch: \", data.errors[0])\n    return res.status(500).send({ success: false, error: \"Unable to fecth sitemap due to an error: \" + data.errors[0].message})\n  }\n\n  const urls = Array.isArray(data.sites) ? data.sites : [];\n  if (urls.length === 0) {\n    return res.status(400).send({ success: false, error: \"No url found on sitemap\" });\n  }\n\n  // let total_count = kbs_count + 1 + urls.length;\n  // if (total_count > kbs_limit) {\n  //   return res.status(403).send({ success: false, error: \"Cannot exceed the number of resources in the current plan\", plan_limit: kbs_limit })\n  // }\n\n  let sitemap_content = {\n    id_project,\n    name: source,\n    source: source,\n    type: 'sitemap',\n    content: \"\",\n    namespace: namespace_id,\n    scrape_type,\n    scrape_options,\n    refresh_rate,\n    ...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})\n  }\n\n  let saved_content;\n  try {\n    saved_content = await KB.findOneAndUpdate({ id_project, type: 'sitemap', source, namespace: namespace_id }, sitemap_content, { upsert: true, new: true }).lean().exec();\n  } catch (err) {\n    winston.error(\"Error saving content: \", err);\n    return res.status(500).send({ success: false, error: err });\n  }\n\n  const options = {\n    sitemap_origin_id: saved_content._id,\n    sitemap_origin: saved_content.source,\n    scrape_type,\n    scrape_options,\n    refresh_rate,\n    ...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})\n  }\n  \n  try {\n    await aiManager.scheduleSitemap(namespace, saved_content, options);\n    let result = await aiManager.addMultipleUrls(namespace, urls, options);\n    if (process.env.NODE_ENV === 'test') {\n      result.result.push(saved_content);\n      return res.status(200).send(result);\n    }\n    result.push(saved_content);\n    return res.status(200).send(result);\n  } catch (err) {\n    return res.status(500).send({ success: false, error: \"Unable to add multiple urls from sitemap due to an error.\" });\n  }  \n  \n})\n\nrouter.put('/:kb_id', async (req, res) => {\n\n  const id_project = req.projectid;\n  const project = req.project;\n  const kb_id = req.params.kb_id;\n  \n  const { name, type, source, content, refresh_rate, scrape_type, scrape_options, tags } = req.body;\n  const namespace_id = req.body.namespace;\n\n  if (!namespace_id) {\n    return res.status(400).send({ success: false, error: \"Missing 'namespace' body parameter\" })\n  }\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(id_project, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  let kb = await KB.findOne({ id_project, namespace: namespace_id, _id: kb_id }).lean().exec();\n\n  if (!kb) {\n    return res.status(404).send({ success: false, error: \"Content not found. Unable to update a non-existing content\" })\n  }\n\n  let data = {\n    id: kb._id,\n    namespace: namespace_id,\n    engine: namespace.engine || default_engine\n  }\n\n  // if (kb.type === 'sitemap') {\n  //   let new_sitemap = {\n  //     id_project,\n  //     name,\n  //     source,\n  //     type: 'sitemap',\n  //     content: \"\",\n  //     namespace: namespace_id,\n  //     status: -1,\n  //     scrape_type,\n  //     scrape_options,\n  //     refresh_rate,\n  //     ...(Array.isArray(tags) && tags.length > 0 ? { tags } : {})\n  //   }\n\n  //   try {\n  //     let result = await aiManager.updateSitemap(id_project, namespace, kb, new_sitemap);\n  //     return res.status(200).send(result);\n  //   } catch (err) {\n  //     winston.error(\"Error updating sitemap: \", err);\n  //     return res.status(500).send({ success: false, error: err });\n  //   }\n  // }\n\n  try {\n    let delete_response = await aiService.deleteIndex(data);\n\n    if (delete_response.data.success === true) {\n      await KB.findOneAndDelete({ id_project, namespace: namespace_id, source }).lean().exec();\n      // continue the flow\n    } else {\n      winston.error(\"Unable to update content due to an error: \", delete_response.data.error);\n      return res.status(500).send({ success: false, error: delete_response.data.error });\n    }\n\n  } catch (err) {\n    winston.error(\"Error updating content: \", err);\n    return res.status(500).send({ success: false, error: err });\n  }\n\n  let new_content = {\n    id_project,\n    name,\n    type,\n    source,\n    content,\n    namespace: namespace_id,\n    status: -1,\n  }\n\n  if (new_content.type === 'url') {\n    new_content.refresh_rate = refresh_rate || 'never';\n    if (scrape_type === 0 || scrape_type === 4) {\n      new_content.scrape_type = scrape_type;\n      new_content.scrape_options = scrape_options;\n    }\n    else {\n      new_content.scrape_type = 2;\n      new_content.scrape_options = aiManager.setDefaultScrapeOptions();\n    }\n  }\n\n  if (kb.sitemap_origin_id) {\n    new_content.sitemap_origin_id = kb.sitemap_origin_id;\n    new_content.sitemap_origin = kb.sitemap_origin;\n  }\n\n  if (tags && Array.isArray(tags) && tags.every(tag => typeof tag === \"string\")) {\n    new_content.tags = tags;\n  }\n\n  winston.debug(\"Update content. New content: \", new_content);\n\n  let updated_content;\n  try {\n    updated_content = await KB.findOneAndUpdate({ id_project, namespace: namespace_id, source }, new_content, { upsert: true, new: true }).lean().exec();\n  } catch (err) {\n    winston.error(\"Error updating content: \", err);\n    return res.status(500).send({ success: false, error: err });\n  }\n\n  const embedding = normalizeEmbedding(namespace.embedding);\n  embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n  let webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;\n  const situated_context = normalizeSituatedContext();\n\n  const json = {\n    id: updated_content._id,\n    type: updated_content.type,\n    source: updated_content.source,\n    content: updated_content.content || \"\",\n    namespace: updated_content.namespace,\n    webhook: webhook,\n    hybrid: namespace.hybrid,\n    engine: namespace.engine || default_engine,\n    embedding: embedding,\n    ...(situated_context && { situated_context: situated_context }),\n    ...(updated_content.scrape_type && { scrape_type: updated_content.scrape_type }),\n    ...(updated_content.scrape_options && { parameters_scrape_type_4: updated_content.scrape_options }),\n    ...(updated_content.tags && { tags: updated_content.tags }),\n  }\n\n  winston.debug(\"json: \", json);\n\n  if (process.env.NODE_ENV === 'test') {\n    return res.status(200).send({ success: true, message: \"Schedule scrape skipped in test environment\", data: updated_content, schedule_json: json });\n  }\n    \n  aiManager.scheduleScrape([json], namespace.hybrid);\n  return res.status(200).send(updated_content);\n\n})\n\nrouter.delete('/:kb_id', async (req, res) => {\n\n  let project_id = req.projectid;\n  let kb_id = req.params.kb_id;\n  winston.verbose(\"delete kb_id \" + kb_id);\n\n  let kb = await KB.findOne({ id_project: project_id, _id: kb_id}).catch((err) => {\n    winston.warn(\"Unable to find kb. Error: \", err);\n    return res.status(500).send({ success: false, error: err })\n  })\n  \n  if (!kb) {\n    winston.error(\"Unable to delete kb. Kb not found...\")\n    return res.status(404).send({ success: false, error: \"Content not found\" })\n  }\n\n  let namespace_id = kb.namespace ?? project_id;\n\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(project_id, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  let data = {\n    id: kb_id,\n    namespace: namespace_id\n  }\n\n  if (!data.namespace) {\n    data.namespace = project_id;\n  }\n  \n  data.engine = namespace.engine || default_engine;\n  winston.verbose(\"/:delete_id data: \", data);\n\n  aiService.deleteIndex(data).then((resp) => {\n    winston.debug(\"delete resp: \", resp.data);\n    if (resp.data.success === true) {\n      KB.findByIdAndDelete(kb_id, (err, deletedKb) => {\n\n        if (err) {\n          winston.error(\"Delete kb error: \", err);\n          return res.status(500).send({ success: false, error: err });\n        }\n        res.status(200).send(deletedKb);\n      })\n\n    } else {\n      winston.verbose(\"resp.data: \", resp.data);\n\n      KB.findOneAndDelete({ _id: kb_id, status: { $in: [-1, 3, 4, 100, 300, 400] } }, (err, deletedKb) => {\n        if (err) {\n          winston.error(\"findOneAndDelete err: \", err);\n          return res.status(500).send({ success: false, error: \"Unable to delete the content due to an error\" })\n        }\n        else if (!deletedKb) {\n          winston.verbose(\"Unable to delete the content in indexing status\")\n          return res.status(500).send({ success: false, error: \"Unable to delete the content in indexing status\" })\n        } else {\n          res.status(200).send(deletedKb);\n        }\n      })\n    }\n\n  }).catch((err) => {\n    let status = err.response?.status || 500;\n    res.status(status).send({ success: false, statusText: err.response.statusText, error: err.response.data.detail });\n  })\n\n})\n\n/**\n* ****************************************\n* Content Section - End\n* ****************************************\n*/\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/kbsettings.js",
    "content": "var express = require('express');\nvar { KBSettings } = require('../models/kb_setting');\n// var { KB } = require('../models/kb_setting');\n// var KB = require('../models/kb_setting')\nvar router = express.Router();\nvar winston = require('../config/winston');\nconst aiService = require('../services/aiService');\n\nrouter.get('/', async (req, res) => {\n    let project_id = req.projectid;\n\n    KBSettings.findOne({ id_project: project_id }, (err, kb_setting) => {\n        if (err) {\n            winston.error(\"find knoledge base settings error: \", err);\n            return res.status(500).send({ success: false, error: err });\n        } \n        else if (!kb_setting) {\n            // Automatically creates the kb settings object if it does not exist\n            let new_settings = new KBSettings({\n                id_project: req.projectid\n            })\n            new_settings.save(function (err, savedKbSettings) {\n                if (err) {\n                    winston.error(\"save new kbs error: \", err);\n                    return res.status(500).send({ success: false, error: err});\n                } else {\n                    return res.status(200).send(savedKbSettings);\n                }\n            })\n        }\n        else {\n            return res.status(200).send(kb_setting)\n        }\n    })\n})\n\n\n// Never used?\nrouter.post('/', async (req, res) => {\n\n    let body = req.body;\n\n    let new_settings = new KBSettings({\n        id_project: req.projectid,\n        // gptkey: body.gptkey\n        // others params are set with default values\n    })\n\n    new_settings.save(function (err, savedKbSettings) {\n        if (err) {\n            winston.error(\"save new kbs error: \", err);\n            return res.status(500).send({ success: false, error: err});\n        } else {\n            return res.status(200).send(savedKbSettings);\n        }\n    })\n})\n\nrouter.put('/:settings_id', async (req, res) => {\n\n    let settings_id = req.params.settings_id;\n    let update = {};\n\n    if (req.body.gptkey != undefined) {\n        update.gptkey = req.body.gptkey\n    }\n    // check if it is eligible\n    // if (req.body.maxKbsNumber != undefined) {\n    //     update.maxKbsNumber = req.body.maxKbsNumber\n    // }\n    // if (req.body.maxPagesNumber != undefined) {\n    //     update.maxPagesNumber = req.body.maxPagesNumber\n    // }\n\n    winston.debug(\"update: \", update);\n    KBSettings.findByIdAndUpdate(settings_id, update, { new: true }, (err, settings) => {\n        if (err) {\n            winston.error(\"findByIdAndUpdate error: \", err);\n            res.status(500).send({ success: false, error: err });\n        } else {\n            res.status(200).send(settings);\n        }\n    })\n})\n\nrouter.delete('/:settings_id/:kb_id', async (req, res) => {\n    \n    let settings_id = req.params.settings_id;\n    let kb_id = req.params.kb_id;\n\n    KBSettings.findByIdAndUpdate(settings_id, { $pull: { kbs: { _id: kb_id }}}, { new: true}, (err, settings) => {\n        if (err) {\n            winston.error(\"delete kb from list error: \", err);\n            res.status(500).send({ success: false, error: err });\n        } else {\n            res.status(200).send(settings);\n        }\n    })\n\n})\n\n\n// PROXY PUGLIA AI - START\nrouter.post('/qa', async (req, res) => {\n    let data = req.body;\n    winston.debug(\"/qa data: \", data);\n\n    aiService.ask(data).then((resp) => {\n        winston.debug(\"qa resp: \", resp.data);\n        res.status(200).send(resp.data);\n    }).catch((err) => {\n        winston.error(\"qa err: \", err);\n        //let status = err.response.status;\n        //res.status(status).send({ statusText: err.response.statusText, detail: err.response.data.detail });\n        res.status(400).send({ success: false, error: err });\n    })\n})\n\nrouter.post('/startscrape', async (req, res) => {\n    \n    let data = req.body;\n    winston.debug(\"/startscrape data: \", data);\n\n    aiService.startScrape(data).then((resp) => {\n        winston.debug(\"startScrape resp: \", resp.data);\n        res.status(200).send(resp.data);\n    }).catch((err) => {\n        winston.error(\"startScrape err: \", err);\n        // let status = err.response.status;\n        // res.status(status).send({ statusText: err.response.statusText, detail: err.response.data.detail });\n        res.status(400).send({ success: false, error: err });\n    })\n})\n\n\nrouter.post('/checkstatus', async (req, res) => {\n\n    //let data = req.body;\n    winston.debug(\"/checkstatus req.body: \", req.body);\n\n    let full_url = req.body.full_url;\n    let data = {\n        url_list: [ req.body.full_url ]\n    }\n\n    aiService.checkStatus(data).then((resp) => {\n        winston.debug(\"checkStatus resp: \", resp);\n        winston.debug(\"checkStatus resp: \", resp.data);\n        winston.debug(\"checkStatus resp: \", resp.data[full_url]);\n\n        let response = resp.data[full_url];\n\n        let return_data = {\n            status_message: response.status_message\n        }\n\n        if (response.status_code === 3) {\n            return_data.status_code = 2;\n        }\n\n        if (response.status_code === 1 || response.status_code === 2 ) {\n            return_data.status_code = 1;\n        }\n\n        if (response.status_code === 0) {\n            return_data.status_code = 0;\n        }\n\n        if (!response.status_code) {\n            return_data.status_code = 0;\n        }\n\n        \n        res.status(200).send(return_data);\n    }).catch((err) => {\n        //winston.error(\"checkstatus err: \", err);\n\n        // let status = err.response?.status;\n        // res.status(status).send({ statusText: err.response?.statusText, detail: err.response?.data?.detail });\n        res.status(400).send({ success: false, error: err });\n    })\n})\n// PROXY PUGLIA AI - END\n\nrouter.post('/:settings_id', async (req, res) => {\n\n    let settings_id = req.params.settings_id;\n    let body = req.body;\n\n    KBSettings.findById(settings_id, (err, settings) => {\n        if (err) {\n            winston.error(\"find knoledge base error: \", err);\n            return res.status(500).send({ success: false, error: err});\n        } else {\n\n            let new_kb = {\n                name: body.name,\n                url: body.url\n            }\n            settings.kbs.push(new_kb);\n\n            KBSettings.findByIdAndUpdate( settings_id, settings, { new: true }, (err, savedSettings) => {\n                if (err) {\n                    winston.err(\"findByIdAndUpdate error: \", err);\n                    res.status(500).send({ success: false, error: err });\n                } else {\n                    res.status(200).send(savedSettings);\n                }\n            })\n        }\n    })\n})\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/key.js",
    "content": "\n'use strict';\n\n// Modules imports\nconst express = require('express');\nvar router = express.Router();\nconst NodeRSA = require('node-rsa');\nconst Project = require('../models/project');\nconst uuidv4 = require('uuid/v4');\nvar winston = require('../config/winston');\n\n// curl -v -X POST -u andrea.leo@f21.it:123456 http://localhost:3000/5bae41325f03b900401e39e8/keys/generate\nrouter.post('/generate', function (req, res) {\n\n    winston.debug('req.projectid', req.projectid);\n\n    // const key = new NodeRSA();\n    // const key = new NodeRSA({b: 512});\n    const key = new NodeRSA({b: 256});\n    const publicKey = key.exportKey('public');\n    const privateKey = key.exportKey('private');\n\n    const jwtSecret = uuidv4();\n   \n    winston.debug('publicKey', publicKey);\n    winston.debug('privateKey', privateKey);\n    winston.debug('jwtSecret', jwtSecret);\n\n    \n\n    var update = {\n        publicKey: publicKey, \n        privateKey: privateKey,\n        jwtSecret: jwtSecret\n    };\n    winston.debug('update', update);\n\n    return Project.findOneAndUpdate({_id: req.projectid, status:100}, update, { new: true, upsert: false }, function (err, updatedProject) {\n        if (err) {\n            winston.error('Error updating project key', err);\n            return res.status(500).send({ success: false, msg: 'Error updating project key.' });\n        }\n        \n        winston.debug('updatedProject', updatedProject);\n\n        if (!updatedProject) {\n            return res.status(404).send({ success: false, msg: 'Project not found' });\n        }\n        else {\n            return res.json({publicKey: publicKey, privateKey: privateKey,jwtSecret: jwtSecret });\n        }\n     });\n        \n\n   \n\n\n  });\n  module.exports = router;"
  },
  {
    "path": "routes/labels-no-default.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Label = require(\"../models/label\");\nvar winston = require('../config/winston');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role')\nvar labelService = require(\"../services/labelService\");\nvar labelEvent = require(\"../event/labelEvent\");\nvar mongoose = require('mongoose');\n\nconst { check, validationResult } = require('express-validator');\n\n\nvar Schema = mongoose.Schema,\n   ObjectId = Schema.ObjectId;\n\n\n\nrouter.get('/default', function (req, res) {\n  \n  res.json(req.labels);\n \n});\n// curl -v -X POST -H 'Content-Type:application/json'  -d '{\"lang\":\"IT\"}' http://localhost:3000/123/labels/default/clone\n\nrouter.post('/default/clone', \n[passport.authenticate(['basic', 'jwt'], { session: false }), \nvalidtoken, \nroleChecker.hasRole('admin'),\ncheck('lang').notEmpty(),  \n],function (req, res, next) {\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n\n  var lang = req.body.lang;\n  winston.debug(\"lang: \" + lang);\n  \n  var pickedLang = req.labels.find(l => l.lang === lang);\n\n  if (!pickedLang){\n    var pickedLangPivot = req.labels.find(l => l.lang === \"EN\");\n    pickedLangPivot.lang = lang;\n    pickedLang = pickedLangPivot;\n  }\n  var newLabel = pickedLang;\n  winston.debug(\"newLabel: \" ,newLabel);\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n        label = new Label({          \n          id_project: req.projectid,\n          createdBy: req.user.id,\n          updatedBy: req.user.id,        \n          data: [newLabel]\n        });\n      }else {\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          label.data[foundIndex] = newLabel;\n        }else {\n          label.data.push(newLabel);\n        }\n      }\n      \n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }        \n          \n          labelEvent.emit('label.clone', savedLabel);\n\n          // express forward          \n          req.url =  '/';\n          winston.debug('--- > req.url'+req.url);\n\n          req.method = 'GET';  \n\n          return router.handle(req, res, next);        \n        });\n    }\n});\n\n   \n        \n});\n \n \n\nrouter.get('/default/:lang', function (req, res) {\n  var pickedLang = req.labels.find(l => l.lang === req.params.lang);\n\n  res.json(pickedLang);\n});\n\n\n\n// curl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"lang\":\"FR\", \"data\":{\"a\":\"b\",\"c\":\"d\"}}' http://localhost:3000/4321/labels/\n\nrouter.post('/',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res, next) {\n \n  var lang = req.body.lang;\n  winston.debug(\"lang: \" + lang);\n\n\n\n  var newLabel = {lang: lang, data: req.body.data};\n  winston.debug(\"newLabel: \" ,newLabel);\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n        label = new Label({          \n          id_project: req.projectid,\n          createdBy: req.user.id,\n          updatedBy: req.user.id,          \n          data: [newLabel]\n        });\n      }else {\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          label.data[foundIndex] = newLabel;\n        }else {\n          label.data.push(newLabel);\n        }\n      }\n      \n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }      \n          labelEvent.emit('label.create', savedLabel);\n\n           // express forward          \n           req.url =  '/'+lang;\n           winston.debug('--- > req.url'+req.url);\n \n           req.method = 'GET';  \n \n           return router.handle(req, res, next);\n        });\n      }\n    });\n\n});\n\n  \n// curl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/4321/labels/\n\nrouter.delete('/',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  winston.debug(req.body);\n\n  Label.remove({ id_project: req.projectid }, function (err, label) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    labelEvent.emit('label.delete', label);\n\n\n    res.json(label);\n  });\n});\n\n// curl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/1235/labels/EN\nrouter.delete('/:lang',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res, next) {\n  var lang = req.params.lang;\n  winston.debug(\"lang: \" + lang);\n\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }else {\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          var idData = label.data[foundIndex]._id;\n          label.data.id(idData).remove();\n        }else {\n          return res.status(404).send({ success: false, msg: 'Object not found.' });\n        }\n      }\n      \n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }\n\n          labelEvent.emit('label.delete', savedLabel);\n\n         \n           req.url =  '/'+lang;\n           winston.debug('--- > req.url'+req.url);\n \n           req.method = 'GET';  \n \n           return router.handle(req, res, next);\n        });\n      }\n    });\n});\n\n\n// return all the project labels merging db with widget.json\nrouter.get('/', function (req, res) {\n \n  var query = { \"id_project\": req.projectid};\n\n  winston.debug(\"query /\", query);\n\n\n  return Label.findOne(query).lean().exec(function (err, labels) {\n\n      if (err) {\n        winston.error('Label ROUTE - REQUEST FIND ERR ', err)\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      winston.debug(\"here /\", labels);\n      let returnval;\n      if (!labels) {\n        winston.debug(\"here  no labels\");\n\n        returnval = {data: req.labels};\n      } else {\n        returnval = labels;\n        // var dataAsObj = {...req.labels, ...labels.data }\n        // var data = Object.values(dataAsObj);\n        req.labels.forEach(elementDef => {\n          var pickedLang = labels.data.find(l => l.lang === elementDef.lang);\n          if (!pickedLang) {\n            returnval.data.push(elementDef);\n          }\n        });\n      \n      }\n      \n      winston.debug(\"returnval\",returnval);\n    \n        return res.json(returnval);\n      \n    });\n});\n\n\n\n\n\nrouter.get('/:lang', async (req, res) => {\n  // get a specific project language merged with default (widget.json) but if not found return Pivot\n  var labels = await labelService.getAllByLanguage(req.projectid, req.params.lang);\n  return res.json(labels);\n\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/labels.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Label = require(\"../models/label\");\nvar winston = require('../config/winston');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role')\nvar labelService = require(\"../services/labelService\");\nvar labelEvent = require(\"../event/labelEvent\");\nvar mongoose = require('mongoose');\n\nconst { check, validationResult } = require('express-validator');\n\n\nwinston.debug(\"  labelService.FALLBACK_LANGUAGE: \" +   labelService.FALLBACK_LANGUAGE  );\n\nvar Schema = mongoose.Schema,\n   ObjectId = Schema.ObjectId;\n\n\n\nrouter.get('/default', function (req, res) {\n  \n  res.json(req.labels);\n \n});\n// curl -v -X POST -H 'Content-Type:application/json'  -d '{\"lang\":\"IT\"}' http://localhost:3000/123/labels/default/clone\n\n// curl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"lang\":\"IT\"}' http://localhost:3001/624c78e27b91d2a2ab49543a/labels/default/clone\n\nrouter.post('/default/clone', \n  [\n    passport.authenticate(['basic', 'jwt'], { session: false }), \n    validtoken, \n    roleChecker.hasRole('admin'),\n    check('lang').notEmpty(),  \n  ],\n  function (req, res, next) {\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n\n  var lang = req.body.lang;\n  winston.debug(\"lang: \" + lang);\n  \n  var pickedLang = req.labels.find(l => l.lang === lang);\n\n  if (!pickedLang) {\n      // FALLBACK_LANGUAGE IMP\n    var pickedLangPivot = req.labels.find(l => l.lang === labelService.FALLBACK_LANGUAGE);\n    pickedLangPivot.lang = lang;\n    pickedLang = pickedLangPivot;\n  }\n  var newLabel = pickedLang;\n  winston.debug(\"newLabel: \" ,newLabel);\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n\n        // auto make default the new lang\n        newLabel.default = true;\n\n        label = new Label({          \n          id_project: req.projectid,\n          createdBy: req.user.id,\n          updatedBy: req.user.id,        \n          data: [newLabel]\n        });\n      }else {\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          label.data[foundIndex] = newLabel;\n        }else {\n          label.data.push(newLabel);\n        }\n      }\n      \n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }        \n          \n          labelEvent.emit('label.clone', savedLabel);\n\n          // express forward          \n          req.url =  '/';\n          winston.debug('--- > req.url'+req.url);\n\n          req.method = 'GET';  \n\n          return router.handle(req, res, next);        \n        });\n    }\n});\n\n   \n        \n});\n \n \n\nrouter.get('/default/:lang', function (req, res) {\n  var pickedLang = req.labels.find(l => l.lang === req.params.lang);\n\n  res.json(pickedLang);\n});\n\n// Update labels\n// curl -v -X PATCH -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"lang\":\"FR\", \"data\":{\"a\":\"b\",\"c\":\"d\"}}' http://localhost:3000/4321/labels/EN/default\nrouter.patch('/:lang/default',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res, next) {\n\n  var lang = req.params.lang;\n  winston.debug(\"lang: \" + lang);\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n        winston.error('Lang not found');\n        return res.status(404).send({ success: false, msg: 'Lang not found' });\n      }else {\n\n        var foundDefaultIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.default === true ) {\n              foundDefaultIndex = index;\n            }\n        });\n\n        winston.debug(\"foundDefaultIndex: \" + foundDefaultIndex);\n        if (foundDefaultIndex>-1) {\n          label.data[foundDefaultIndex].default = false;\n          winston.debug(\"label.data[foundDefaultIndex]: \", label.data[foundDefaultIndex]);\n        }\n        \n\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          label.data[foundIndex].default = true;\n        }\n\n        \n      \n      \n        winston.debug(\"saving label \", label);\n\n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }      \n          labelEvent.emit('label.update', savedLabel);\n\n           // express forward          \n           req.url =  '/'+lang;\n           winston.debug('--- > req.url'+req.url);\n \n           req.method = 'GET';  \n \n           return router.handle(req, res, next);\n        });\n      }\n\n    }\n  });\n  \n\n});\n\n// Update labels\n// curl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"lang\":\"FR\", \"data\":{\"a\":\"b\",\"c\":\"d\"}}' http://localhost:3000/4321/labels/\nrouter.post('/',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res, next) {\n \n  var lang = req.body.lang;\n  winston.debug(\"lang: \" + lang);\n\n  var defaultValue =  req.body.default;\n  winston.debug(\"defaultValue: \" + defaultValue);\n\n  var newLabel = {lang: lang, data: req.body.data, default: defaultValue};\n  winston.debug(\"newLabel: \" ,newLabel);\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n\n        // auto make default the new lang\n        newLabel.default = true;\n\n        label = new Label({          \n          id_project: req.projectid,\n          createdBy: req.user.id,\n          updatedBy: req.user.id,          \n          data: [newLabel]\n        });\n      }else {\n        //remove existing default\n\n        if (defaultValue===true) {\n          winston.debug(\"defaultValue: \" + defaultValue);\n          var foundDefaultIndex = -1;\n          label.data.forEach(function(l, index){\n              if (l.default === true ) {\n                foundDefaultIndex = index;\n              }\n          });\n\n          winston.debug(\"foundDefaultIndex: \" + foundDefaultIndex);\n          if (foundDefaultIndex>-1) {\n            label.data[foundDefaultIndex].default = false;\n            winston.debug(\"label.data[foundDefaultIndex]: \", label.data[foundDefaultIndex]);\n          }\n        }\n\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          label.data[foundIndex] = newLabel;\n        }else {\n          label.data.push(newLabel);\n        }\n\n        \n      }\n      \n      winston.debug(\"saving label \", label);\n\n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }      \n          labelEvent.emit('label.create', savedLabel);\n\n           // express forward          \n           req.url =  '/'+lang;\n           winston.debug('--- > req.url'+req.url);\n \n           req.method = 'GET';  \n \n           return router.handle(req, res, next);\n        });\n      }\n    });\n\n});\n\n  \n// curl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/4321/labels/\n\nrouter.delete('/',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  winston.debug(req.body);\n\n  Label.remove({ id_project: req.projectid }, function (err, label) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    labelEvent.emit('label.delete', label);\n\n\n    res.json(label);\n  });\n});\n\n// curl -v -X DELETE -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/1235/labels/EN\nrouter.delete('/:lang',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res, next) {\n  var lang = req.params.lang;\n  winston.debug(\"lang: \" + lang);\n\n\n  Label.findOne({id_project:req.projectid}, function(err, label) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    } else {\n      if (!label) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }else {\n        var foundIndex = -1;\n        label.data.forEach(function(l, index){\n            if (l.lang == lang ) {\n              foundIndex = index;\n            }\n        });\n        winston.debug(\"foundIndex: \" + foundIndex);\n        if (foundIndex>-1) {\n          var labelFound = label.data[foundIndex];\n          if (labelFound.default === true) {\n            return res.status(400).send({ success: false, msg: 'You can\\'t delete the default language.' });\n          }\n          var idData = labelFound._id;\n          label.data.id(idData).remove();\n        }else {\n          return res.status(404).send({ success: false, msg: 'Object not found.' });\n        }\n      }\n      \n        label.save(function (err, savedLabel) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            return res.status(500).send({ success: false, msg: 'Error saving object.' });\n          }\n\n          labelEvent.emit('label.delete', savedLabel);\n\n         \n           req.url =  '/'+lang;\n           winston.debug('--- > req.url'+req.url);\n \n           req.method = 'GET';  \n \n           return router.handle(req, res, next);\n        });\n      }\n    });\n});\n\n\n// return all the project labels\nrouter.get('/', async (req, res)  => {\n \n\n  winston.debug(\"projectid\", req.projectid);\n\n\n   var labels = await labelService.getAll(req.projectid);\n   winston.debug(\"labels\",labels);\n    \n   if (!labels) {\n    winston.warn('Label not found for projectid: ' + req.projectid);\n    return res.json({});\n  }\n\n  return res.json(labels);\n          \n});\n\n\n\n\n\nrouter.get('/:lang', async (req, res) => {\n  // get a specific project language but if not found return Pivot\n  var labels = await labelService.getLanguage(req.projectid, req.params.lang);\n  return res.json(labels);\n\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/labelsSingle.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Label = require(\"../models/label\");\nvar winston = require('../config/winston');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role')\n\n\n// router.post('/',  function (req, res) {\nrouter.post('/',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n if (Array.isArray(req.body)) {\n\n  req.body.forEach(function(lab,index) {\n    var newLabel = new Label({\n      lang: lab.lang,\n      key: lab.key,\n      message: lab.message,\n      id_project: req.projectid,\n      createdBy: req.user.id,\n      updatedBy: req.user.id\n    });\n\n    newLabel.save(function (err, savedLabel) {\n      if (err) {\n        winston.error('--- > ERROR ', err);\n        return res.status(500).send({ success: false, msg: 'Error saving object.' });\n      }\n      \n    });\n\n\n    res.json('{\"success\":true}');\n  });\n\n  // Label.insertMany(req.body, function (err, savedLabels) {\n  //   if (err) {\n  //     winston.error('--- > ERROR ', err);\n  //     return res.status(500).send({ success: false, msg: 'Error saving object.' });\n  //   }\n  //   res.json(savedLabels);\n  // });\n\n }else {\n\n  var newLabel = new Label({\n    lang: req.body.lang,\n    key: req.body.key,\n    message: req.body.message,\n    id_project: req.projectid,\n    createdBy: req.user.id,\n    updatedBy: req.user.id\n  });\n\n  newLabel.save(function (err, savedLabel) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n    res.json(savedLabel);\n  });\n\n }\n\n  \n});\n\n// router.post('/bulk',  function (req, res) {\n\n//   winston.debug(req.body);\n//   winston.debug(\"req.user\", req.user);\n\n \n\n// });\n\nrouter.put('/:labelid',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res) {\n  winston.debug(req.body);\n\n  Label.findByIdAndUpdate(req.params.labelid, req.body, { new: true, upsert: true }, function (err, updatedLabel) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    res.json(updatedLabel);\n  });\n});\n\nrouter.delete('/:labelid',  [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')],function (req, res) {\n  winston.debug(req.body);\n\n  Label.remove({ _id: req.params.labelid }, function (err, label) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    res.json(label);\n  });\n});\n\n\n\nrouter.get('/:lang', function (req, res) {\n \n  var query = { \"id_project\": req.projectid,\"lang\": req.params.lang};\n\n \n  winston.debug(\"query\", query);\n\n  return Label.find(query).  \n    exec(function (err, labels) {\n      if (err) {\n        winston.error('Label ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n    \n        return res.json(labels);\n      \n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/lead.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Lead = require(\"../models/lead\");\nvar LeadConstants = require(\"../models/leadConstants\");\nvar winston = require('../config/winston');\nvar leadService = require(\"../services/leadService\");\ncsv = require('csv-express');\ncsv.separator = ';';\nconst leadEvent = require('../event/leadEvent');\nvar Segment = require(\"../models/segment\");\nvar Segment2MongoConverter = require(\"../utils/segment2mongoConverter\");\n\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  leadService.createWitId(req.body.lead_id, req.body.fullname, req.body.email, req.projectid, req.user.id, req.body.attributes).then(function(savedLead) {\n    res.json(savedLead);\n  })\n\n});\n\nrouter.put('/:leadid', function (req, res) {\n  winston.debug(req.body);\n  var update = {};\n  \n    if (req.body.fullname!=undefined) {\n      update.fullname = req.body.fullname;\n    }\n    \n    if (req.body.email!=undefined) {\n      update.email = req.body.email;\n    }\n    if (req.body.attributes!=undefined) {\n      update.attributes = req.body.attributes;\n    }\n    if (req.body.status!=undefined) {\n      update.status = req.body.status;\n    }\n\n\n    if (req.body.phone!=undefined) {\n      update.phone = req.body.phone;\n    }\n    if (req.body.company!=undefined) {\n      update.company = req.body.company;\n    }\n    if (req.body.note!=undefined) {\n      update.note = req.body.note;\n    }\n    \n    if (req.body.streetAddress!=undefined) {\n      update.streetAddress = req.body.streetAddress;\n    }\n    if (req.body.city!=undefined) {\n      update.city = req.body.city;\n    }\n    if (req.body.region!=undefined) {\n      update.region = req.body.region;\n    }\n    if (req.body.zipcode!=undefined) {\n      update.zipcode = req.body.zipcode;\n    }\n    if (req.body.country!=undefined) {\n      update.country = req.body.country;\n    }  \n\n    if (req.body.tags) {\n      update.tags = req.body.tags;\n    }\n    \n\n  \n  Lead.findByIdAndUpdate(req.params.leadid, update, { new: true, upsert: true }, function (err, updatedLead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n  \n\n    leadEvent.emit('lead.update', updatedLead);\n\n    if (req.body.fullname!=undefined) {\n      leadEvent.emit('lead.fullname.update', updatedLead);\n    }\n    \n    if (req.body.email!=undefined) {\n      leadEvent.emit('lead.email.update', updatedLead);\n    }\n\n    if (req.body.email!=undefined || req.body.fullname!=undefined) {\n      leadEvent.emit('lead.fullname.email.update', updatedLead);\n    }\n  \n    res.json(updatedLead);\n  });\n});\n\n\nrouter.put('/:leadid/tag', async (req, res) => {\n\n  let lead_id = req.params.leadid;\n  let tags_list = req.body;\n  winston.debug(\"(Lead) /tag tags_list: \", tags_list)\n  \n  if (tags_list.length == 0) {\n    winston.warn(\"(Lead) /tag no tag specified\")\n    return res.status(400).send({ success: false, message: \"No tag specified\" })\n  }\n\n  let lead = await Lead.findById(lead_id).catch((err) => {\n    winston.error(\"(Lead) /tag error getting lead \", err);\n    return res.status(500).send({ success: false, error: \"Error getting lead with id \" + lead_id});\n  })\n\n  if (!lead) {\n    winston.warn(\"(Lead) /tag lead not found with lead_id \" + lead_id);\n    return res.status(404).send({ success: false, error: \"Lead not found with id \" + lead_id });\n  }\n\n  let current_tags = lead.tags;\n  tags_list.forEach(t => {\n    // Check if tag already exists in the lead. If true, skip the adding.\n    if (!current_tags.includes(t)) {\n      current_tags.push(t)\n    }\n  })\n\n  let update = {\n    tags: current_tags\n  }\n\n  Lead.findByIdAndUpdate(lead_id, update, { new: true }, (err, updatedLead) => {\n    if (err) {\n      winston.error(\"(Lead) /tag error finding and update lead \", err);\n      return res.status(500).send({ success: false, error: \"Error updating lead with id \" + lead_id })\n    }\n\n    if (!updatedLead) {\n      winston.warn(\"(Lead) /tag The lead was deleted while adding tags for lead \" + lead_id);\n      return res.status(404).send({ success: false, error: \"The lead was deleted while adding tags for lead \" + lead_id })\n    }\n\n    winston.debug(\"(Lead) /tag Lead updated successfully \", updatedLead);\n    \n    leadEvent.emit('lead.update', updatedLead);\n    res.status(200).send(updatedLead)\n\n  })\n})\n\n\nrouter.patch('/:leadid/attributes',  function (req, res) {\n  var data = req.body;\n\n  // TODO use service method\n\n  Lead.findById(req.params.leadid, function (err, lead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n     if (!lead) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      \n      if (!lead.attributes) {\n        winston.debug(\"empty attributes\")\n        lead.attributes = {};\n      }\n\n      winston.debug(\" lead attributes\", lead.attributes)\n        \n        Object.keys(data).forEach(function(key) {\n          var val = data[key];\n          winston.debug(\"data attributes \"+key+\" \" +val)\n          lead.attributes[key] = val;\n        });     \n        \n        winston.debug(\" lead attributes\", lead.attributes)\n\n        // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n        lead.markModified('attributes');\n\n          //cacheinvalidation\n          lead.save(function (err, savedLead) {\n          if (err) {\n            winston.error(\"error saving lead attributes\",err)\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          winston.verbose(\" saved lead attributes\",savedLead.toObject())\n          leadEvent.emit('lead.update', savedLead);\n\n            res.json(savedLead);\n        });\n  });\n  \n});\n\n\n//.post and .patch for /properties are equals\n\nrouter.post('/:leadid/properties',  function (req, res) {\n  var data = req.body;\n\n  // TODO use service method\n\n  Lead.findById(req.params.leadid, function (err, lead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n     if (!lead) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      \n      if (!lead.properties) {\n        winston.debug(\"empty properties\")\n        lead.properties = {};\n      }\n\n      winston.debug(\" lead properties\", lead.properties)\n        \n        Object.keys(data).forEach(function(key) {\n          var val = data[key];\n          winston.debug(\"data attributes \"+key+\" \" +val)\n          lead.properties[key] = val;\n        });     \n        \n        winston.debug(\" lead properties\", lead.properties)\n\n        // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n        lead.markModified('properties');\n\n          //cacheinvalidation\n          lead.save(function (err, savedLead) {\n          if (err) {\n            winston.error(\"error saving lead properties\",err)\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          winston.verbose(\" saved lead properties\",savedLead.toObject())\n          leadEvent.emit('lead.update', savedLead);\n\n            res.json(savedLead);\n        });\n  });\n  \n});\n\n\nrouter.patch('/:leadid/properties',  function (req, res) {\n  var data = req.body;\n\n  // TODO use service method\n\n  Lead.findById(req.params.leadid, function (err, lead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n     if (!lead) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      \n      if (!lead.properties) {\n        winston.debug(\"empty properties\")\n        lead.properties = {};\n      }\n\n      winston.debug(\" lead properties\", lead.properties)\n        \n        Object.keys(data).forEach(function(key) {\n          var val = data[key];\n          winston.debug(\"data attributes \"+key+\" \" +val)\n          lead.properties[key] = val;\n        });     \n        \n        winston.debug(\" lead properties\", lead.properties)\n\n        // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n        lead.markModified('properties');\n\n          //cacheinvalidation\n          lead.save(function (err, savedLead) {\n          if (err) {\n            winston.error(\"error saving lead properties\",err)\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          winston.verbose(\" saved lead properties\",savedLead.toObject())\n          leadEvent.emit('lead.update', savedLead);\n\n            res.json(savedLead);\n        });\n  });\n  \n});\n\n\n\n// router.put('/:leadid', function (req, res) {\n//   winston.debug(req.body);\n//   var update = {};\n  \n//   // trasforma in patch altrimenti nn va\n//     update.fullname = req.body.fullname;\n//     update.email = req.body.email;\n//     update.attributes = req.body.attributes;\n//     update.status = req.body.status;\n  \n  \n//   Lead.findByIdAndUpdate(req.params.leadid, update, { new: true, upsert: true }, function (err, updatedLead) {\n//     if (err) {\n//       winston.error('--- > ERROR ', err);\n//       return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//     }\n\n  \n\n//     leadEvent.emit('lead.update', updatedLead);\n//     res.json(updatedLead);\n//   });\n// });\nrouter.delete('/:leadid/tag/:tag', async (req, res) => {\n\n  let lead_id = req.params.leadid;\n  let tag = req.params.tag;\n\n  Lead.findByIdAndUpdate(lead_id, { $pull: { tags: tag }}, { new: true}).then((updatedLead) => {\n    if (!updatedLead) {\n      winston.warn(\"(Lead) /removetag lead not found with id \" + lead_id);\n      return res.status(404).send({ success: false, error: \"Lead not found with id \" + lead_id })\n    }\n\n    winston.debug(\"(Lead) /removetag updatedLead: \", updatedLead)\n    res.status(200).send(updatedLead)\n  }).catch((err) => {\n    winston.error(\"(Lead) /removetag error updating lead: \", err);\n    res.status(500).send({ success: false, error: err });\n  })\n})\n\n\nrouter.delete('/:leadid', function (req, res) {\n  winston.debug(req.body);\n\n  Lead.findByIdAndUpdate(req.params.leadid, {status: LeadConstants.DELETED}, { new: true, upsert: true }, function (err, updatedLead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n   \n\n    leadEvent.emit('lead.delete', updatedLead);\n    res.json(updatedLead);\n  });\n});\n\nrouter.delete('/:leadid/physical', function (req, res) {\n  winston.debug(req.body);\n\n  var projectuser = req.projectuser;\n\n\n  if (projectuser.role != \"owner\" ) {\n    return res.status(403).send({ success: false, msg: 'Unauthorized.' });\n  }\n  \n   // TODO use findByIdAndRemove otherwise lead don't contains label object\n  Lead.remove({ _id: req.params.leadid }, function (err, lead) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n  \n    leadEvent.emit('lead.delete', lead);\n\n    res.json(lead);\n  });\n});\n\n\n// DOWNLOAD leads AS CSV\nrouter.get('/csv', function (req, res, next) {\n  var limit = 100000; // Number of leads per page\n  var page = 0;\n  if (req.query.page) {\n    page = req.query.page;\n  }\n  var skip = page * limit;\n  winston.debug('LEAD ROUTE - SKIP PAGE ', skip);\n\n  var query = { \"id_project\": req.projectid, \"status\": {$lt: LeadConstants.DELETED}};\n\n  if (req.query.full_text) {\n    winston.debug('LEAD ROUTE req.query.fulltext', req.query.full_text);\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  if (req.query.email) {\n    winston.debug('LEAD ROUTE req.query.email', req.query.email);\n    query.email = req.query.email;\n  }\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  winston.debug(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  winston.debug(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  // TODO ORDER BY SCORE\n  // return Faq.find(query,  {score: { $meta: \"textScore\" } }) \n  // .sort( { score: { $meta: \"textScore\" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n\n\n  // Lead.find({ \"id_project\": req.projectid }, function (err, leads, next) {\n  return Lead.find(query, '-attributes -__v').\n    skip(skip).limit(limit).\n    sort(sortQuery).lean().\n    exec(function (err, leads) {\n      if (err) {\n        winston.error('LEAD ROUTE - EXPORT CONTACT TO CSV ERR ', err)\n        return next(err);\n      }\n      winston.verbose('LEAD ROUTE - EXPORT CONTACT TO CSV LEADS', leads)      \n      \n      return res.csv(leads, true);\n    });\n});\n\nrouter.get('/:leadid', function (req, res) {\n  winston.debug(req.body);\n\n  Lead.findById(req.params.leadid, function (err, lead) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!lead) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(lead);\n  });\n});\n\n\nrouter.get('/', async(req, res) => {\n\n  var limit = 40; // Number of request per page\n\n  if (req.query.limit) {    \n    limit = parseInt(req.query.limit);\n    winston.debug('LEAD ROUTE - limit: '+limit);\n  }\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('LEAD ROUTE - SKIP PAGE ', skip);\n\n\n  var query = {};\n\n\n  if (req.query.segment) {\n    let segment = await Segment.findOne({id_project: req.projectid, _id: req.query.segment }).exec();\n    if (!segment) {\n      return res.status(404).send({ success: false, msg: 'Error segment not found' });\n    }\n    Segment2MongoConverter.convert(query, segment);\n  }\n  \n  if (req.query.full_text) {\n    winston.debug('LEAD ROUTE req.query.fulltext', req.query.full_text);\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  if (req.query.email) {\n    winston.debug('LEAD ROUTE req.query.email', req.query.email);\n    query.email = req.query.email;\n  }\n\n  if (req.query.with_email) {  //for internal request and zapier to retrieve only lead with an email\n    winston.debug('LEAD ROUTE req.query.withemail', req.query.withemail);\n    query.email = { \"$exists\": true };\n  }\n\n  if (req.query.with_fullname) {  //or internal request to retrieve only lead with an email\n    winston.debug('LEAD ROUTE req.query.withfullname', req.query.with_fullname);\n    query.fullname = { \"$exists\": true };\n  }\n\n  // aggiungi filtro per data\n\n\n  if (req.query.tags) {\n    winston.debug('req.query.tags', req.query.tags);\n    query[\"tags\"] = req.query.tags;\n  }\n\n\n  // last query modifier\n  query[\"id_project\"] = req.projectid;\n  query[\"status\"] = LeadConstants.NORMAL;\n\n  if (req.query.status) {\n    query.status = req.query.status;\n  }\n\n  winston.debug(\"query\", query);\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  // TODO ORDER BY SCORE\n  // return Faq.find(query,  {score: { $meta: \"textScore\" } }) \n  // .sort( { score: { $meta: \"textScore\" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n\n\n  // aggiungi filtro per data marco\n\n  return Lead.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, leads) {\n      if (err) {\n        winston.error('LEAD ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n      // blocked to 1000 TODO increases it\n      //  collection.count is deprecated, and will be removed in a future version. Use Collection.countDocuments or Collection.estimatedDocumentCount instead\n      return Lead.countDocuments(query, function (err, totalRowCount) {\n\n        var objectToReturn = {\n          perPage: limit,\n          count: totalRowCount,\n          leads: leads\n        };\n\n        return res.json(objectToReturn);\n      });\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/llm.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\nlet Integration = require('../models/integrations');\nconst aiService = require('../services/aiService');\nconst multer = require('multer');\nconst fileUtils = require('../utils/fileUtils');\nconst { MODELS_MULTIPLIER } = require('../utils/aiUtils');\n\nlet MAX_UPLOAD_FILE_SIZE = process.env.MAX_UPLOAD_FILE_SIZE;\nlet uploadlimits = undefined;\n\nif (MAX_UPLOAD_FILE_SIZE) {\n  uploadlimits = {fileSize: parseInt(MAX_UPLOAD_FILE_SIZE)} ;\n  winston.debug(\"Max upload file size is : \" + MAX_UPLOAD_FILE_SIZE);\n} else {\n  winston.debug(\"Max upload file size is infinity\");\n}\nvar upload = multer({limits: uploadlimits});\n\nrouter.post('/preview', async (req, res) => {\n\n    let id_project = req.projectid;\n    let body = req.body;\n    let key;\n    let publicKey = false;\n\n    if (!body.llm) {\n        return res.status(400).send({ success: false, error: \"Missing required parameter 'llm'\" });\n    }\n\n    let integration = await Integration.findOne({ id_project: id_project, name: body.llm }).catch((err) => {\n        winston.error(\"Error finding integration with name: \", body.llm);\n        return res.status(500).send({ success: false, error: \"Error finding integration for \" + body.llm});\n    })\n\n    if (!integration) {\n        winston.verbose(\"Integration not found for \" + body.llm)\n        if (body.llm === \"openai\") {\n            winston.verbose(\"Try to retrieve shared OpenAI key\")\n            if (!process.env.GPTKEY) {\n                winston.error(\"Shared key for OpenAI not configured.\");\n                return res.status(404).send({ success: false, error: \"No key found for \" + body.llm });\n            }\n            key = process.env.GPTKEY;\n            publicKey = true;\n            winston.verbose(\"Using shared OpenAI key as fallback.\");\n        } else {\n            winston.verbose(\"Integration for \" + body.llm + \" not found.\")\n            return res.status(404).send({ success: false, error: \"Integration for \" + body.llm + \" not found.\" })\n        }\n    } else {\n        if (!integration?.value?.apikey && body.llm !== \"ollama\") {\n            return res.status(422).send({ success: false, error: \"The key provided for \" + body.llm + \" is not valid or undefined.\" });\n        }\n        key = integration.value.apikey;\n    }\n\n    let obj = { createdAt: new Date() };\n\n    let quoteManager = req.app.get('quote_manager');\n    if (publicKey === true) {\n        let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');\n        if (isAvailable === false) {\n            return res.status(403).send({ success: false, message: \"Tokens quota exceeded\", error_code: 13001})\n        }\n    }\n\n    let json = {\n        question: body.question,\n        llm: body.llm,\n        model: body.model,\n        llm_key: key,\n        temperature: body.temperature,\n        max_tokens: body.max_tokens\n    }\n\n    if (body.context) {\n        json.system_context = body.context;\n    }\n\n    if (body.llm === 'ollama') {\n        json.llm_key = \"\";\n        json.model = {\n            name: body.model,\n            url: integration.value.url,\n            token: integration.value.token\n        },\n        json.stream = false;\n    }\n\n    winston.debug(\"Preview LLM json: \", json);\n\n    aiService.askllm(json).then((response) => {\n        winston.verbose(\"Askllm response: \", response);\n        if (publicKey === true) {\n            let multiplier = MODELS_MULTIPLIER[json.model];\n            if (!multiplier) {\n                multiplier = 1;\n                winston.info(\"No multiplier found for AI model (llm) \" + json.model)\n            }\n            obj.multiplier = multiplier;\n            obj.tokens = response.data?.prompt_token_info?.total_tokens || 0;\n            quoteManager.incrementTokenCount(req.project, obj);\n        }\n        res.status(200).send(response.data)\n    }).catch((err) => {\n        if (err.response?.data?.detail?.[0]) {\n            res.status(400).send({ success: false, error: err.response.data.detail[0]?.msg, detail: err.response.data.detail });\n        } else if (err.response?.data?.detail?.answer) {\n            res.status(400).send({ success: false, error: err.response.data.detail.answer, detail: err.response.data.detail });\n        } else if (err.response?.data) {\n            res.status(500).send({ success: false, error: err.response.data });\n        } else {\n            res.status(500).send({ success: false, error: err });\n        }\n    })\n\n})\n\nrouter.post('/transcription', upload.single('uploadFile'), async (req, res) => {\n\n    let id_project = req.projectid;\n\n    let file;\n    if (req.body.url) {\n        file = await fileUtils.downloadFromUrl(req.body.url);\n    } else if (req.file) {\n        file = req.file.buffer;\n    } else {\n        return res.status(400).send({ success: false, error: \"No audio file or URL provided\"})\n    }\n\n    let key;\n\n    let integration = await Integration.findOne({ id_project: id_project, name: 'openai' }).catch((err) => {\n        winston.error(\"Error finding integration for openai\");\n        return res.status(500).send({ success: false, error: \"Error finding integration for openai\"});\n    })\n    if (!integration) {\n        winston.verbose(\"Integration for openai not found.\")\n        return res.status(404).send({ success: false, error: \"Integration for openai not found.\"})\n    }\n    if (!integration?.value?.apikey) {\n        return res.status(422).send({ success: false, error: \"The key provided for openai is not valid or undefined.\" })\n    }\n\n    key = integration.value.apikey;\n\n    aiService.transcription(file, key).then((response) => {\n        winston.verbose(\"Transcript response: \", response.data);\n        res.status(200).send({ text: response.data.text});\n    }).catch((err) => {\n        winston.error(\"err: \", err.response?.data)\n        res.status(500).send({ success: false, error: err });\n    })\n\n})\n\n  \nmodule.exports = router;\n"
  },
  {
    "path": "routes/logs.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\nconst { MessageLog } = require('../models/whatsappLog');\nconst { Transaction } = require('../models/transaction');\nconst logsService = require('../services/logsService');\nconst { v4: uuidv4 } = require('uuid');\nconst jwt = require(\"jsonwebtoken\")\n\nconst jwtSecret = process.env.CHAT21_JWT_SECRET || \"tokenKey\";\n\nrouter.get('/', function (req, res, next) {\n    winston.info(\"logs\", req.body);\n    return res.status(200).send({ success: true });\n});\n\n\nrouter.post('/', function (req, res, next) {\n    winston.info(\"logs\", req.body);\n    return res.status(200).send({ success: true });\n});\n\nrouter.get('/whatsapp', async (req, res) => {\n\n    let project_id = req.projectid;\n\n    Transaction.find({ id_project: project_id, broadcast: { $in: [null, true] } }, (err, transactions) => {\n        if (err) {\n            winston.error(\"Error find transactions for project_id: \" + project_id);\n            return res.status(400).send({ success: false, message: \"Unable to find transaction for project_id \" + project_id });\n        }\n\n        winston.verbose(\"Transactions: \", transactions);\n\n        res.status(200).send(transactions);\n    })\n\n})\n\n\nrouter.get('/whatsapp/:transaction_id', async (req, res) => {\n\n    let project_id = req.projectid;\n\n    let transaction_id = req.params.transaction_id;\n    winston.info(\"Get logs for whatsapp transaction_id \" + transaction_id);\n\n    MessageLog.find({ id_project: project_id, transaction_id: transaction_id }).lean().exec((err, logs) => {\n        if (err) {\n            winston.error(\"Error find logs for transaction_id \" + transaction_id);\n            return res.status(400).send({ success: false, message: \"Unable to find logs for transaction_id \" + transaction_id })\n        }\n\n        winston.verbose(\"Logs found: \", logs);\n\n        let clearLogs = logs.map(({ _id, __v, ...keepAttrs }) => keepAttrs)\n        winston.verbose(\"clearLogs: \", clearLogs)\n\n        res.status(200).send(clearLogs);\n    })\n\n})\n\nrouter.get('/whatsapp/user/:phone_number', async (req, res) => {\n    \n    const id_project = req.projectid;\n    const phone_number = req.params.phone_number;\n    \n    let query = { id_project: id_project, \"json_message.to\": phone_number };\n\n    MessageLog.find(query).lean().exec((err, logs) => {\n        if (err) {\n            winston.error(\"Error find logs for phone_number \" + phone_number);\n            return res.status(400).send({ success: false, message: \"Unable to find logs for phone_number \" + phone_number })\n        }\n        winston.verbose(\"Logs found: \", logs);\n\n        let clearLogs = logs.map(({ _id, __v, ...keepAttrs }) => keepAttrs)\n        winston.verbose(\"clearLogs: \", clearLogs)\n\n        res.status(200).send(clearLogs);\n    })\n\n})\n\nrouter.post('/whatsapp', async (req, res) => {\n\n    winston.info(\"save following log: \", req.body);\n\n    let log = new MessageLog({\n        id_project: req.body.id_project,\n        json_message: req.body.json_message,\n        transaction_id: req.body.transaction_id,\n        message_id: req.body.message_id,\n        status: req.body.status,\n        status_code: req.body.status_code,\n        error: req.body.error\n    });\n\n    log.save((err, savedLog) => {\n        if (err) {\n            winston.error(\"Unable to save log: \", err);\n            return res.status(400).send(err);\n        }\n\n        winston.info(\"savedLog: \", savedLog);\n        res.status(200).send(savedLog);\n    })\n})\n\n\nrouter.get('/flows/:id', async (req, res) => {\n    const id = req.params.id;\n    const { timestamp, direction, logLevel, type } = req.query;\n\n    if (!id) {\n        return res.status(400).send({ success: false, error: \"Missing required parameter 'id'.\" });\n    }\n\n    // Determine if we're searching by request_id or webhook_id\n    const isWebhook = type === 'webhook';\n    const queryField = isWebhook ? 'webhook_id' : 'request_id';\n\n    let method;\n\n    if (!timestamp) {\n        method = logsService.getLastRows(id, 20, logLevel, queryField);\n    } else if (direction === 'prev') {\n        method = logsService.getOlderRows(id, 10, logLevel, new Date(timestamp), queryField);\n    } else if (direction === 'next') {\n        method = logsService.getNewerRows(id, 10, logLevel, new Date(timestamp), queryField);\n    } else {\n        return res.status(400).send({ success: false, error: \"Missing or invalid 'direction' parameter. Use 'prev' or 'next'.\"});\n    }\n\n    method.then((logs) => {\n        res.status(200).send(logs);\n    }).catch((err) => {\n        res.status(500).send({ success: false, error: \"Error fetching logs: \" + err.message });\n    });\n});\n\n\nrouter.get('/flows/auth/:request_id', async (req, res) => {\n\n    const request_id = req.params.request_id;\n    const appid = \"tilechat\";\n\n    const scope = [\n        `rabbitmq.read:*/*/apps.${appid}.logs.${request_id}.*`,\n        `rabbitmq.write:*/*/apps.${appid}.logs.${request_id}.*`,\n        `rabbitmq.configure:*/*/*`\n    ]\n\n    const now = Math.round(new Date().getTime() / 1000);\n    const exp = now + 60 * 60 * 24 * 30;\n\n    var payload = {\n        \"jti\": uuidv4(),\n        \"sub\": request_id,\n        scope: scope,\n        \"client_id\": request_id,\n        \"cid\": request_id,\n        \"azp\": request_id,\n        \"user_id\": request_id,\n        \"app_id\": appid,\n        \"iat\": now,\n        \"exp\": exp,\n        \"aud\": [\n            \"rabbitmq\",\n            request_id\n        ],\n        \"kid\": \"tiledesk-key\",\n    }\n\n    var token = jwt.sign(\n        payload,\n        jwtSecret,\n        {\n            \"algorithm\": \"HS256\"\n        }\n    );\n\n    const result = {\n        request_id: request_id,\n        token: token\n    }\n\n    return res.status(200).send(result);\n\n}) \n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/mcp.js",
    "content": "let express = require('express');\nlet router = express.Router();\nlet winston = require('../config/winston');\nconst mcpService = require('../services/mcpService');\nconst Project = require('../models/project');\n\n/**\n * POST /mcp/connect\n * Initializes a connection to an MCP server\n * Body: { url: string, auth?: { type: 'bearer'|'api_key'|'basic', token?: string, key?: string, username?: string, password?: string } }\n */\nrouter.post('/connect', async (req, res) => {\n  try {\n    const id_project = req.projectid;\n    const { url, auth } = req.body;\n\n    if (!url) {\n      return res.status(400).send({ success: false, error: \"Missing required parameter 'url'\" });\n    }\n\n    // Build server configuration\n    const serverConfig = {\n      url: url,\n      projectId: id_project,\n      auth: auth || undefined\n    };\n\n    // Initialize the connection\n    const result = await mcpService.initializeServer(serverConfig);\n\n    res.status(200).send({ \n      success: true, \n      message: 'MCP server connected successfully',\n      capabilities: result?.capabilities || {}\n    });\n  } catch (error) {\n    winston.error(`Error connecting to MCP server:`, error);\n    res.status(500).send({ success: false, error: error.message || 'Error connecting to MCP server' });\n  }\n});\n\n/**\n * POST /mcp/tools\n * Gets the list of tools from an MCP server\n * Body: { url: string, auth?: object }\n */\nrouter.post('/tools', async (req, res) => {\n  try {\n    const id_project = req.projectid;\n    const { url, auth } = req.body;\n\n    if (!url) {\n      return res.status(400).send({ success: false, error: \"Missing required parameter 'url' in body\" });\n    }\n\n    // Build server configuration\n    const serverConfig = {\n      url: url,\n      projectId: id_project,\n      auth: auth || undefined\n    };\n\n    // listTools automatically initializes if necessary\n    const tools = await mcpService.listTools(serverConfig);\n\n    res.status(200).send(tools);\n  } catch (error) {\n    winston.error(`Error getting tools from MCP server:`, error);\n    res.status(500).send({ success: false, error: error.message || 'Error getting tools from MCP server' });\n  }\n});\n\nmodule.exports = router;\n\n"
  },
  {
    "path": "routes/message.js",
    "content": "var express = require('express');\n\n// https://stackoverflow.com/questions/28977253/express-router-undefined-params-with-router-use-when-split-across-files\nvar router = express.Router({mergeParams: true});\n\nvar Message = require(\"../models/message\");\nvar Request = require(\"../models/request\");\nvar Lead = require(\"../models/lead\");\n\nvar requestService = require('../services/requestService');\nvar messageService = require('../services/messageService');\nvar leadService = require('../services/leadService');\nvar winston = require('../config/winston');\n\nvar MessageConstants = require(\"../models/messageConstants\");\n\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\nconst { check, validationResult } = require('express-validator');\n\nvar Project_user = require(\"../models/project_user\");\nvar mongoose = require('mongoose');\nvar PromiseUtil = require(\"../utils/promiseUtil\");\n\ncsv = require('csv-express');\ncsv.separator = ';';\n\n// var roleChecker = require('../middleware/has-role');\n\nrouter.post('/', \n// [\n//   check('text').custom(value => {\n//     console.log(\"value\",value);\n//     console.log(\"req.body.type\",this.type);\n//     if (this.type === \"text\" && (value == undefined || value == \"\" ) ) {    \n//       // if (this.type === \"text\" && ( (!value) || (value === \"\") ) ) {    \n//       console.log(\"sono qui \",value);\n//       return Promise.reject('Text field is required for text message');\n//     }else {\n//       console.log(\"sono qua \",value);\n//       return Promise.resolve();\n//     }\n//   })\n// ],\nasync (req, res)  => {\n\n  winston.debug('req.body post message', req.body);\n  winston.debug('req.params: ', req.params);\n  winston.debug('req.params.request_id: ' + req.params.request_id);\n\n  // const errors = validationResult(req);\n  // if (!errors.isEmpty()) {\n  //   return res.status(422).json({ errors: errors.array() });\n  // }\n\n  // TODO se sei agent non puoi cambiare sender\n  // verificare validazione invio immagine senza caption \n  var project_user = req.projectuser;\n  var sender = req.body.sender;\n  var fullname = req.body.senderFullname || req.user.fullName;\n  var email = req.body.email || req.user.email;\n\n  let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n  winston.debug('messageStatus: ' + messageStatus);\n\n  let q = Request.findOne({request_id: req.params.request_id, id_project: req.projectid}); \n  if (cacheEnabler.request) {\n    q.cache(cacheUtil.defaultTTL, req.projectid+\":requests:request_id:\"+req.params.request_id+\":simple\") //request_cache\n    winston.debug('request cache enabled');\n  }\n  // cacherequest       // requestcachefarequi nocachepopulatereqired\n  return q.exec(async(err, request) => {\n    // .populate('lead')\n    // .populate('department')\n    // .populate('participatingBots')\n    // .populate('participatingAgents')  \n    // .populate({path:'requester',populate:{path:'id_user'}})\n    \n    if (err) {\n      winston.log({\n          level: 'error',\n          message: 'Error getting the request: '+ JSON.stringify(err) + \" \" + JSON.stringify(req.body) ,\n          label: req.projectid\n        });\n      // winston.error('Error getting the request.', err);\n      return res.status(500).send({success: false, msg: 'Error getting the request.', err:err});\n    }\n\n    if (!request) { //the request doen't exists create it\n\n          winston.debug(\"request not exists\", request);                                     \n\n          if (project_user) {\n            winston.debug(\"project_user\", project_user);                                     \n          }\n          \n          \n            // sponz: 4/01/23 disable it\n            if (!req.body.text &&  (!req.body.type || req.body.type==\"text\") ) {\n              return res.status(422).json({ errors: [\"text field is required\"] });\n            } \n\n          if (sender) {\n\n            var isObjectId = mongoose.Types.ObjectId.isValid(sender);\n            winston.debug(\"isObjectId:\"+ isObjectId);\n        \n              var queryProjectUser = {id_project:req.projectid, status: \"active\" };\n        \n            if (isObjectId) {\n              queryProjectUser.id_user = sender;\n            } else {\n              queryProjectUser.uuid_user = sender;\n            }\n        \n            winston.debug(\"queryProjectUser\", queryProjectUser);\n            \n            project_user = await Project_user.findOne(queryProjectUser).populate({path:'id_user', select:{'firstname':1, 'lastname':1, 'email':1}})\n            winston.debug(\"project_user\", project_user);\n        \n            if (!project_user) {\n              return res.status(403).send({success: false, msg: 'Unauthorized. Project_user not found with user id  : '+ sender });\n            }\n\n            if ( project_user.id_user) {\n              fullname = project_user.id_user.fullName;\n              winston.debug(\"pu fullname: \"+ fullname);\n              email = project_user.id_user.email;\n              winston.debug(\"pu email: \"+ email);\n            } else if (project_user.uuid_user) {\n              var lead = await Lead.findOne({lead_id: project_user.uuid_user, id_project: req.projectid});\n              winston.debug(\"lead: \",lead);\n              if (lead) {\n                fullname = lead.fullname;\n                winston.debug(\"lead fullname: \"+ fullname);\n                email = lead.email;\n                winston.debug(\"lead email: \"+ email);\n              }else {\n                winston.warn(\"lead not found: \" + JSON.stringify({lead_id: project_user.uuid_user, id_project: req.projectid}));\n              }\n              \n            } else {\n              winston.warn(\"pu fullname and email empty\");\n            }\n            \n          }\n\n          // prende fullname e email da quello loggato\n\n          // createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes) {\n          return leadService.createIfNotExistsWithLeadId(sender || req.user._id, fullname, email, req.projectid, null, req.body.attributes || req.user.attributes, undefined, req.user.phone)\n          .then(function(createdLead) {\n\n            const contact = {\n              ...(createdLead.phone && { phone: createdLead.phone }),\n              ...(createdLead.email && { email: createdLead.email })\n            };\n        \n            var new_request = {                                     \n              request_id: req.params.request_id, \n              project_user_id: project_user._id,\n              lead_id: createdLead._id, \n              ...(Object.keys(contact).length && { contact }),\n              id_project:req.projectid,\n              first_text: req.body.text, \n              departmentid: req.body.departmentid, \n              sourcePage:req.body.sourcePage, \n              language: req.body.language, \n              userAgent:req.body.userAgent, \n              status:req.body.request_status, \n              createdBy: req.user._id,\n              attributes: req.body.attributes, \n              subject: req.body.subject, \n              preflight:req.body.preflight, \n              channel: req.body.channel, \n              location: req.body.location,\n              participants: req.body.participants,\n              lead: createdLead, \n              requester: project_user,\n              priority: req.body.priority,\n              followers: req.body.followers,\n              proactive: true\n            };\n\n            return requestService.create(new_request).then(function (savedRequest) {\n\n\n              if (!savedRequest) {\n                return res.status(403).send({ success: false, message: \"Requests quota exceeded\"})\n              }\n              winston.debug(\"returning savedRequest to\", savedRequest.toJSON());\n\n              // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, \n              //  createdBy, attributes, subject, preflight, channel, location) {\n\n              // return requestService.createWithIdAndRequester(req.params.request_id, req.projectuser._id, createdLead._id, req.projectid, \n              //   req.body.text, req.body.departmentid, req.body.sourcePage, \n              //   req.body.language, req.body.userAgent, null, req.user._id, req.body.attributes, req.body.subject, undefined, req.body.channel, req.body.location ).then(function (savedRequest) {\n\n\n\n\n              // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type, channel) {\n              return messageService.create(sender || req.user._id, fullname, req.params.request_id, req.body.text,\n                req.projectid, req.user._id, messageStatus, req.body.attributes, req.body.type, req.body.metadata, req.body.language, undefined, req.body.channel).then(function (savedMessage) {\n\n                  // return requestService.incrementMessagesCountByRequestId(savedRequest.request_id, savedRequest.id_project).then(function(savedRequestWithIncrement) {\n\n                  let message = savedMessage.toJSON();\n\n                  winston.debug(\"returning message to\", message);\n\n                  winston.debug(\"returning savedRequest2210 to\", savedRequest.toJSON());\n\n\n                  savedRequest //bug\n                    // Request.findById(savedRequest.id)\n                    .populate('lead')\n                    .populate('department')\n                    .populate('participatingBots')\n                    .populate('participatingAgents')\n                    // .populate('followers')  \n                    .populate({ path: 'requester', populate: { path: 'id_user' } })\n                    // .exec(function (err, savedRequestPopulated){    \n                    .execPopulate(function (err, savedRequestPopulated) {   //bug with  execPopulate request.attributes are invalid (NOT real data). but this bug is related to chat21 listener changes by reference. i think populate suffer from this problem bacause it it the same obect passed by reference \n\n                      if (err) {\n                        return winston.error(\"Error gettting savedRequestPopulated for send Message\", err);\n                      }\n\n                      winston.debug(\"returning savedRequest221 to\", savedRequest.toJSON());\n\n\n                      winston.debug(\"savedRequestPopulated\", savedRequestPopulated.toJSON());\n\n                      winston.debug(\"returning savedRequest22 to\", savedRequest.toJSON());\n\n\n                      message.request = savedRequestPopulated;\n                      winston.debug(\"returning2 message to\", message);\n\n\n                      return res.json(message);\n                    });\n                });\n            }).catch(function (err) {    //pubblica questo\n              winston.error('Error creating request: ' + JSON.stringify(err));\n              return res.status(500).send({ success: false, msg: 'Error creating request', err: err });\n            });\n\n          });\n                        \n\n\n    } else {\n\n      winston.debug(\"request  exists\", request.toObject());\n      if (request.channel?.name === 'form') {\n        if (!sender && request.participantsAgents?.[0] !== req.user.id) {\n          return res.status(403).send({ success: false, message: \"Error creating message\", err: \"You don't belong the conversation\" });\n        }\n      }\n      \n      return messageService.create(sender || req.user._id, fullname, req.params.request_id, req.body.text,\n        request.id_project, null, messageStatus, req.body.attributes, req.body.type, req.body.metadata, \n        req.body.language, undefined, req.body.channel).then(function(savedMessage){\n\n          // TOOD update also request attributes and sourcePage\n          // return requestService.incrementMessagesCountByRequestId(request.request_id, request.id_project).then(function(savedRequest) {\n          Request.findOneAndUpdate({request_id: request.request_id, id_project: request.id_project}, { \"attributes.last_message\": savedMessage}).catch((err) => {\n            winston.error(\"Create message - saved last message in request error: \", error);\n          })\n\n              \n          if (request.participants && request.participants.indexOf(sender) > -1) { //update waiitng time if write an  agent (member of participants)\n            winston.debug(\"updateWaitingTimeByRequestId\");\n            return requestService.updateWaitingTimeByRequestId(request.request_id, request.id_project, true).then(function(upRequest) {\n                let message = savedMessage.toJSON();\n                message.request = upRequest;\n                return res.json(message);\n            });\n\n          } else {\n\n            let message = savedMessage.toJSON();\n            winston.debug(\"getting request for response\");\n\n            let q = Request.findOne({request_id:  request.request_id, id_project: request.id_project})\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')  \n              .populate({path:'requester',populate:{path:'id_user'}});\n\n            if (cacheEnabler.request) {\n              q.cache(cacheUtil.defaultTTL, request.id_project+\":requests:request_id:\"+request.request_id)\n              winston.debug('request cache enabled for messages');\n            }\n\n            q.exec(function (err, requestPopulated) {    \n \n              if (err) {\n                return winston.error(\"Error gettting savedRequestPopulated for send Message\", err);\n              }   \n              message.request = requestPopulated;\n              return res.json(message);\n            });                     \n          }\n        }).catch(function(err) {\n          winston.log({\n            level: 'error',\n            message: 'Error creating message endpoint: '+ JSON.stringify(err) + \" \" + JSON.stringify(req.body) ,\n            label: req.projectid\n          });\n          winston.error(\"Error creating message\", err);\n          return res.status(500).send({success: false, msg: 'Error creating message', err:err });\n        });\n    }\n  });\n});\n\n\n\n\n\n\n// router.post('/multi2', async (req, res, next)  => {\n\n//       if ( !req.body instanceof Array ) {\n//               return res.status(422).json({ errors: [\"request body is not array\"] });\n//       }\n//           req.url =  '/';\n//           winston.info('--- > req.url'+req.url);\n\n//           req.method = 'POST';  \n\n//           let promises = [];\n\n//           req.body.forEach(function(message,index) {\n//             promises.push(router.handle(req, res, next));\n//           });\n//           winston.info('--- >promises', promises);\n\n//           Promise.all(promises).then((values) => {\n//             console.log(\"finito\",values);\n//             return res.status(200).json({ \"success\": true });\n//           }).catch((err) => {\n//             console.log(\"errore\",err);\n//           });\n// });\n\n\n\n\nrouter.post('/multi', \nasync (req, res)  => {\n\n  winston.debug('req.body post message', req.body);\n  winston.debug('req.params: ', req.params);\n  winston.debug('req.params.request_id: ' + req.params.request_id);\n\n  const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    return res.status(422).json({ errors: errors.array() });\n  }\n  \n  var project_user = req.projectuser;\n\n  let q = Request.findOne({request_id: req.params.request_id, id_project: req.projectid}); \n  // if (cacheEnabler.request) {\n  //   q.cache(cacheUtil.defaultTTL, req.projectid+\":requests:request_id:\"+req.params.request_id+\":simple\") //request_cache\n  //   winston.debug('request cache enabled');\n  // }\n  return q.exec(async(err, request) => {\n    \n\n    if (err) {\n      winston.log({\n          level: 'error',\n          message: 'Error getting the request: '+ JSON.stringify(err) + \" \" + JSON.stringify(req.body) ,\n          label: req.projectid\n        });\n      // winston.error('Error getting the request.', err);\n      return res.status(500).send({success: false, msg: 'Error getting the request.', err:err});\n    }\n\n    if (!request) { //the request doen't exists create it\n      return res.status(404).send({success: false, msg: 'Request doesn\\'t exist.', err:err});\n    } else {\n\n  \n\n      winston.debug(\"request  exists\", request.toObject());\n  \n      \n      let promises = [];\n\n      let sender;\n      let fullname;\n      let email;\n      let text;\n      let attributes;\n      let type;\n      let metadata;\n      let language;\n      let channel;\n      let messageStatus;\n      \n      req.body.forEach(function(message,index) {\n\n        sender = message.sender;\n        fullname = message.senderFullname || req.user.fullName;\n        email = message.email || req.user.email;\n\n        text = message.text;\n        attributes = message.attributes;\n        type = message.type;\n        metadata = message.metadata;\n        language = message.language;\n        channel = message.channel;\n        messageStatus = message.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type, channel) {                 \n        let promise =  messageService.create(sender || req.user._id, fullname, req.params.request_id, text,\n                request.id_project, null, messageStatus, attributes, type, metadata, language, undefined, channel);\n\n          promises.push(promise);\n      });\n      winston.debug('--- >promises', promises);\n\n      //Promise.all(promises).then((values) => {\n      PromiseUtil.doAllSequentually(promises).then((values) => {\n        winston.info('Inserted multiple messages with values: ', values);\n        return res.status(200).json(values);\n      }).catch((err) => {\n        winston.error('Error inserting multiple messages.', err);\n        return res.status(500).send({success: false, msg: 'Error inserting multiple messages.', err:err});\n\n      });\n\n      \n\n\n\n\n    }\n  \n\n\n  });\n\n\n\n\n});\n\n\n\n\n\n\n\n\n\n\n// router.patch('/:messageid', function(req, res) {\n  \n//   winston.info(req.body);\n  \n//   Message.findByIdAndUpdate(req.params.messageid, req.body, {new: true, upsert:true}, function(err, updatedMessage) {\n//     if (err) {\n//       return res.status(500).send({success: false, msg: 'Error updating object.'});\n//     }\n//     res.json(updatedMessage);\n//   });\n// });\n\n\n// router.put('/:messageid', function(req, res) {\n  \n//     console.log(req.body);\n    \n//     Message.findByIdAndUpdate(req.params.messageid, req.body, {new: true, upsert:true}, function(err, updatedMessage) {\n//       if (err) {\n//         return res.status(500).send({success: false, msg: 'Error updating object.'});\n//       }\n//       res.json(updatedMessage);\n//     });\n//   });\n\n\n//   router.delete('/:messageid', function(req, res) {\n  \n//     console.log(req.body);\n    \n//     Message.remove({_id:req.params.messageid}, function(err, Message) {\n//       if (err) {\n//         return res.status(500).send({success: false, msg: 'Error deleting object.'});\n//       }\n//       res.json(Message);\n//     });\n//   });\n\n  router.get('/csv', function(req, res) {\n\n    // console.log(\"csv\");\n\n\n    return Message.find({\"recipient\": req.params.request_id, id_project: req.projectid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) return next(err);\n      res.csv(messages, true);\n    });\n  });\n\n  router.get('/:messageid', function(req, res) {\n  \n    // console.log(req.body);\n    \n    Message.findById(req.params.messageid, function(err, message) {\n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n      if(!message){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n      res.json(message);\n    });\n  });\n\n\n\nrouter.get('/', function(req, res) {\n\n  return Message.find({\"recipient\": req.params.request_id, id_project: req.projectid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) return next(err);\n      res.json(messages);\n    });\n});\n\n\n\n\nmodule.exports = router;"
  },
  {
    "path": "routes/messagesRoot.js",
    "content": "var express = require('express');\n\n// https://stackoverflow.com/questions/28977253/express-router-undefined-params-with-router-use-when-split-across-files\nvar router = express.Router({mergeParams: true});\n\nvar MessageConstants = require(\"../models/messageConstants\");\nvar Message = require(\"../models/message\");\nvar Request = require(\"../models/request\");\nvar messageService = require(\"../services/messageService\");\nvar winston = require('../config/winston');\nvar fastCsv = require(\"fast-csv\");\nvar roleChecker = require('../middleware/has-role');\nconst { check, validationResult } = require('express-validator');\n\n\nrouter.post('/', \n[\n  check('recipient').notEmpty(),  \n  check('recipientFullname').notEmpty(),\n  check('text').custom((value, { req }) => {\n    winston.debug('validation: '+ value + ' req.body.type ' + req.body.type);\n    if (!value && (!req.body.type || req.body.type === \"text\") ) {\n      winston.debug('validation1 ');\n      return Promise.reject('Text field is required for messages with type \"text\"');\n    }   \n    winston.debug('validation2 ');\n    return Promise.resolve('validation ok');\n  })\n],\n  async (req, res)  => {\n\n  winston.debug('req.body post message', req.body);\n  winston.debug('req.params: ', req.params);\n  winston.debug('req.params.request_id: ' + req.params.request_id);\n\n\n   const errors = validationResult(req);\n  if (!errors.isEmpty()) {\n    winston.error(\"Signup validation error\", errors);\n    return res.status(422).json({ errors: errors.array() });\n  }\n  let message = {\n    sender: req.body.sender || req.user._id, \n    senderFullname: req.body.senderFullname || req.user.fullName, \n    recipient: req.body.recipient, \n    recipientFullname: req.body.recipientFullname,\n    text: req.body.text, \n    id_project: req.projectid, // rendilo opzionale?\n    createdBy: req.user._id, \n    status:  MessageConstants.CHAT_MESSAGE_STATUS.SENDING, \n    attributes: req.body.attributes, \n    type: req.body.type, \n    metadata: req.body.metadata, \n    language: req.body.language, \n    channel_type: req.body.channel_type || MessageConstants.CHANNEL_TYPE.DIRECT, \n    channel: req.body.channel\n};\n  return messageService.save(message).then(function(savedMessage){                    \n      res.json(savedMessage);\n    }).catch(function(err){                    \n      winston.error('Error saving message.', err);\n      return res.status(500).send({ success: false, msg: 'Error saving message.', err: err });\n    })\n\n});\n\n\n\nrouter.get('/',roleChecker.hasRoleOrTypes('owner'), function(req, res) {\n\n  const DEFAULT_LIMIT = 10;\n\n  var limit = DEFAULT_LIMIT; // Number of rows per page\n\n  // console.log(\"req.query.populate_request\",req.query.populate_request);\n  if (req.query.limit) {\n    limit = parseInt(req.query.limit);\n  }\n  if (limit > 50) {\n    limit = DEFAULT_LIMIT;\n  }\n\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n\n  return Message.find({id_project: req.projectid}).sort({createdAt: 'desc'}).\n    skip(skip).limit(limit).lean().exec(async (err, messages) => { \n      if (err) return next(err);\n\n      if (req.query.populate_request) {  \n        // console.log(\"pupulate\")\n        for (var i = 0; i < messages.length; i++) {\n          messages[i].request = await Request.findOne({request_id:messages[i].recipient, id_project: messages[i].id_project }).exec();\n        }\n      }\n\n      res.json(messages);\n    });\n});\n\nrouter.get('/csv', roleChecker.hasRoleOrTypes('owner'), function(req, res) {\n\n\n  const cursor = Message.find({id_project: req.projectid}).select(\"-channel -attributes -metadata\");\n\n  const transformer = (doc)=> {\n    return {\n        Id: doc._id,\n        Name: doc.fullname,\n        Email: doc.email,\n        Type: doc.registration_type,\n        RegisterOn: doc.registered_on\n    };\n  }\n\n  const filename = 'export.csv';\n\n  res.setHeader('Content-disposition', `attachment; filename=${filename}`);\n  res.writeHead(200, { 'Content-Type': 'text/csv' });\n\n  res.flushHeaders();\n\n  console.log(\"fastCsv\",fastCsv)\n  var csvStream = fastCsv.format({headers: true})//.transform(transformer)\n  // var csvStream = fastCsv.createWriteStream({headers: true}).transform(transformer)\n  cursor.stream().pipe(csvStream).pipe(res);\n\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/openai.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar { KBSettings } = require('../models/kb_setting');\nvar aiService = require('../services/aiService');\nvar winston = require('../config/winston');\nconst { QuoteManager } = require('../services/QuoteManager');\nconst { MODELS_MULTIPLIER } = require('../utils/aiUtils');\n\nrouter.post('/', async (req, res) => {\n\n    let project_id = req.projectid;\n    let body = req.body;\n    let usePublicKey = false;\n    let publicKey = process.env.GPTKEY;\n    let gptkey = null;\n    let obj = { createdAt: new Date() };\n    let quoteManager = req.app.get('quote_manager');\n\n    KBSettings.findOne({ id_project: project_id }, async (err, kbSettings) => {\n\n        if (err) {\n            usePublicKey = true;\n            gptkey = publicKey;\n        }\n\n        if (kbSettings && kbSettings.gptkey) {\n            gptkey = kbSettings.gptkey;\n        } else {\n            usePublicKey = true;\n            gptkey = publicKey;\n        }\n\n        if (!gptkey) {\n            return res.status(400).send({ success: false, message: \"Missing gptkey parameter\" });\n        }\n\n        if (usePublicKey === true) {\n            let isAvailable = await quoteManager.checkQuote(req.project, obj, 'tokens');\n            if (isAvailable === false) {\n                return res.status(403).send({ success: false, message: \"Tokens quota exceeded\", error_code: 13001})\n            }\n        }\n        \n\n        let json = {\n            model: body.model,\n            messages: [\n                { role: \"user\", content: body.question }\n            ],\n            max_tokens: body.max_tokens,\n            temperature: body.temperature\n        }\n\n        let message = { role: \"\", content: \"\" };\n        if (body.context) {\n            message.role = \"system\";\n            message.content = body.context;\n            json.messages.unshift(message);\n        }\n\n        if (body.formatType && body.formatType !== 'none') {\n            json.response_format = { \n                type: body.formatType \n            }\n        }\n\n        let multiplier = MODELS_MULTIPLIER[json.model];\n        if (!multiplier) {\n            multiplier = 1;\n            winston.info(\"No multiplier found for AI model (openai) \" + json.model)\n        }\n\n        aiService.completions(json, gptkey).then(async (response) => {\n            let data = { createdAt: new Date(), tokens: response.data.usage.total_tokens, multiplier: multiplier }\n            if (usePublicKey === true) {\n                let incremented_key = await quoteManager.incrementTokenCount(req.project, data);\n                winston.verbose(\"Tokens quota incremented for key \" + incremented_key);\n            }\n\n            res.status(200).send(response.data);\n\n        }).catch((err) => {\n            // winston.error(\"completions error: \", err);\n            res.status(500).send(err)\n        })\n    })\n})\n\nrouter.post('/quotes', async (req, res) => {\n\n    let project = req.project;\n\n    let body = req.body;\n    body.createdAt = new Date(body.createdAt);\n\n    let redis_client = req.app.get('redis_client');\n    if (!redis_client) {\n        return res.status(400).send({ error: \"Redis not ready\"});\n    }\n\n    let quoteManager = req.app.get('quote_manager');\n\n    let incremented_key = await quoteManager.incrementTokenCount(req.project, req.body);\n    let quote = await quoteManager.getCurrentQuote(req.project, req.body, 'tokens');\n    \n    res.status(200).send({ message: \"value incremented for key \" + incremented_key, key: incremented_key, currentQuote: quote });\n})\n\n// router.get('/', async (req, res) => {\n\n//     let project_id = req.projectid;\n\n//     OpenaiKbs.find({ id_project: project_id }, (err, kbs) => {\n//         if (err) {\n//             console.error(\"find all kbs error: \", err);\n//             return res.status(500).send({ success: false, error: err });\n//         } else {\n//             return res.status(200).send(kbs);\n//         }\n//     })\n// })\n\n// router.post('/', async (req, res) => {\n\n//     let body = req.body;\n\n//     let new_kbs = new OpenaiKbs({\n//         name: body.name,\n//         url: body.url,\n//         id_project: req.projectid,\n//         gptkey: req.body.gptkey\n//     })\n\n//     new_kbs.save(function (err, savedKbs) {\n//         if (err) {\n//             console.error(\"save new kbs error: \", err);\n//             return res.status(500).send({ success: false, error: err});\n//         } else {\n//             return res.status(200).send(savedKbs);\n//         }\n//     })\n// })\n\n// router.put('/', async (req, res) => {\n//     // to be implemented\n// })\n\n// router.delete('/:kbs_id', async (req, res) => {\n//     let kbs_id = req.params.kbs_id;\n\n//     OpenaiKbs.findOneAndDelete( { _id: kbs_id }, (err, kbDeleted) => {\n//         if (err) {\n//             console.error(\"find one and delete kbs error: \", err);\n//             return res.status(500).send({ success: false, error: err});\n//         } else {\n//             return res.status(200).send({ success: true, message: 'Knowledge Base deleted successfully', openai_kb: kbDeleted });\n//         }\n//     })\n// })\n\nmodule.exports = router;"
  },
  {
    "path": "routes/pending-invitation.js",
    "content": "var PendingInvitation = require(\"../models/pending-invitation\");\nvar express = require('express');\nvar router = express.Router();\n\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\n// var pendingInvitationService = require(\"../services/pendingInvitationService\");\nvar emailService = require(\"../services/emailService\");\nvar winston = require('../config/winston');\n\nrouter.get('/resendinvite/:pendinginvitationid', function (req, res) {\n\n  PendingInvitation.findById(req.params.pendinginvitationid, function (err, pendinginvitation) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!pendinginvitation) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    if (pendinginvitation) {\n      winston.debug('RESEND INVITE TO THE PENDING INVITATION: ', pendinginvitation);\n      winston.debug('RESEND INVITE - CURRENT PROJECT: ', req.project);\n      winston.debug('RESEND INVITE - CURRENT USER: ', req.user);\n\n      emailService.sendInvitationEmail_UserNotRegistered(pendinginvitation.email, req.user.firstname, req.user.lastname, req.project.name, req.project._id, pendinginvitation.role, pendinginvitation._id)\n      //                                                         // invited_user_email, currentUserFirstname, currentUserLastname, project_name, project_id, invited_user_role\n      // return pendingInvitationService.saveInPendingInvitation(pendinginvitation.email, req.user.firstname, req.user.lastname, req.project.name, req.project._id,  pendinginvitation.role)\n      // .then(function (savedPendingInvitation) {\n      //   return res.json({ msg: \"User not found, save invite in pending \", pendingInvitation: savedPendingInvitation });\n      // })\n      // .catch(function (err) {\n      //   return res.send(err);\n      //   // return res.status(500).send(err);\n      // });\n\n    }\n    res.json({ 'Resend invitation email to': pendinginvitation });\n  });\n});\n\n\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n\n  var email = req.body.email;\n  if (email) {\n    email = email.toLowerCase();\n  }\n  var newPendingInvitation = new PendingInvitation({\n    email: email,\n    role: req.body.role,\n    id_project: req.id_projectid,\n    createdBy: req.user.id,\n    updatedBy: req.user.id\n  });\n\n  newPendingInvitation.save(function (err, savedPendingInvitation) {\n    if (err) {\n      winston.error('--- > ERROR ', err)\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n    res.json(savedPendingInvitation);\n  });\n});\n\nrouter.put('/:pendinginvitationid', function (req, res) {\n\n  winston.debug('PENDING INVITATION UPDATE - BODY ', req.body);\n\n  var update = {};\n\n  var email = req.body.email;\n  if (email) {\n    email = email.toLowerCase();\n  }\n\n  update.email = email;\n  update.role = req.body.role;\n\n  PendingInvitation\n    .findByIdAndUpdate(req.params.pendinginvitationid, update, { new: true, upsert: true },\n      function (err, updatedPendingInvitation) {\n        if (err) {\n          return res.status(500).send({ success: false, msg: 'Error updating object.' });\n        }\n        res.json(updatedPendingInvitation);\n      });\n});\n\n\nrouter.delete('/:pendinginvitationid', function (req, res) {\n\n  winston.debug('PENDING INVITATION DELETE - BODY ', req.body);\n\n  PendingInvitation.remove({ _id: req.params.pendinginvitationid }, function (err, pendinginvitation) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n    res.json(pendinginvitation);\n  });\n});\n\n\n\nrouter.get('/:pendinginvitationid', function (req, res) {\n\n  winston.debug('PENDING INVITATION GET BY ID - BODY ', req.body);\n\n  PendingInvitation.findById(req.params.pendinginvitationid, function (err, pendinginvitation) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!pendinginvitation) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(pendinginvitation);\n  });\n});\n\n\n\n\nrouter.get('/', function (req, res) {\n\n  winston.debug(\"GET PENDING INVITATION - req projectid\", req.projectid);\n\n  PendingInvitation.find({ \"id_project\": req.projectid }, function (err, pendinginvitation) {\n    if (err) {\n      winston.error('GET PENDING INVITATION ERROR ', err);\n      return (err);\n    }\n    if (!pendinginvitation) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug('GET PENDING INVITATION ', pendinginvitation);\n\n    res.json(pendinginvitation);\n  });\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/project.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Project = require(\"../models/project\");\nvar projectEvent = require(\"../event/projectEvent\");\nvar projectService = require(\"../services/projectService\");\nvar projectUserService = require(\"../services/projectUserService\");\n\nvar Project_user = require(\"../models/project_user\");\n\nvar operatingHoursService = require(\"../services/operatingHoursService\");\nvar Department = require('../models/department');\nvar Group = require('../models/group');\n\nvar winston = require('../config/winston');\nvar roleChecker = require('../middleware/has-role');\nvar config = require('../config/database');\n\n// THE THREE FOLLOWS IMPORTS  ARE USED FOR AUTHENTICATION IN THE ROUTE\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar RoleConstants = require(\"../models/roleConstants\");\nvar cacheUtil = require('../utils/cacheUtil');\nvar orgUtil = require(\"../utils/orgUtil\");\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar mongoose = require('mongoose');\n\nvar jwt = require('jsonwebtoken');\n// CHECK IT ASAP!!!!\nlet configSecret = process.env.GLOBAL_SECRET || config.secret;\nvar pKey = process.env.GLOBAL_SECRET_OR_PRIVATE_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n  configSecret = pKey.replace(/\\\\n/g, '\\n');\n}\n\nlet pubConfigSecret = process.env.GLOBAL_SECRET || config.secret;\nvar pubKey = process.env.GLOBAL_SECRET_OR_PUB_KEY;\nif (pubKey) {\n  pubConfigSecret = pubKey.replace(/\\\\n/g, '\\n');\n}\n// CHECK IT ASAP!!!!\n\n\nrouter.post('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], async (req, res) => {\n  \n  // create(name, createdBy, settings) \n  return projectService.create(req.body.name, req.user.id, undefined, req.body.defaultLanguage).then(function(savedProject) {\n      res.json(savedProject);\n  });\n  \n});\n\n// DOWNGRADE PLAN. UNUSED\nrouter.put('/:projectid/downgradeplan', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('owner')], function (req, res) {\n  winston.debug('downgradeplan - UPDATE PROJECT REQ BODY ', req.body);\n  Project.findByIdAndUpdate(req.params.projectid, req.body, { new: true, upsert: true }, function (err, updatedProject) {\n      if (err) {\n          winston.error('Error putting project ', err);\n          return res.status(500).send({ success: false, msg: 'Error updating object.' });\n      }\n      projectEvent.emit('project.downgrade', updatedProject );\n      res.json(updatedProject);\n  });\n});\n\n\nrouter.delete('/:projectid/physical', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('owner')], function (req, res) {\n  winston.debug(req.body);\n  // TODO delete also department, faq_kb, faq, group, label, lead, message, project_users, requests, subscription\n  \n  // TODO use findByIdAndRemove otherwise project don't contains label object\n  Project.remove({ _id: req.params.projectid }, function (err, project) {\n      if (err) {\n          winston.error('Error deleting project ', err);\n          return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n      }\n      projectEvent.emit('project.delete', project );\n      res.json(project);\n  });\n});\n\nrouter.delete('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('owner')], function (req, res) {\n  winston.debug(req.body);\n  // TODO delete also department, faq_kb, faq, group, label, lead, message, project_users, requests, subscription\n  Project.findByIdAndUpdate(req.params.projectid, {status:0}, { new: true, upsert: true }, function (err, project) {\n      if (err) {\n          winston.error('Error deleting project ', err);\n          return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n      }\n      projectEvent.emit('project.delete', project );\n      res.json(project);\n  });\n});\n\n// router.put('/:projectid/update', function (req, res) {\n// // router.put('/:projectid/profile', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], function (req, res) {\n  \n//   // Get token from header authorization\n//   let token = req.headers.authorization;\n//   token = token.split(\" \")[1];\n\n//   try {\n//     let decoded = jwt.verify(token, configSecret)\n//     winston.debug(\"user decode: \", decoded);\n\n//     if (!process.env.ADMIN_EMAIL) {\n//       winston.warn(\"Missing admin email parameter in environment\");\n//       return res.status(401).send({ success: false, error: \"Missing admin email parameter\"});\n//     }\n\n//     if (decoded.email !== process.env.ADMIN_EMAIL) {\n//       winston.warn(\"Profile modification: permission denied.\");\n//       return res.status(403).send({ success: false, error: \"You don't have the permission required to modify the project profile\"});\n//     } \n\n//     /**\n//      * modify the project profile here\n//      */\n//     var update = {};\n\n//     if (req.body.name!=undefined) {\n//       update.name = req.body.name;\n//     }\n  \n//     if (req.body.activeOperatingHours!=undefined) {\n//       update.activeOperatingHours = req.body.activeOperatingHours;\n//     }\n    \n//     if (req.body.operatingHours!=undefined) {\n//       update.operatingHours = req.body.operatingHours;\n//     }\n    \n//     if (req.body.settings!=undefined) {\n//       update.settings = req.body.settings;\n//     }\n\n//     if (req.body[\"settings.email.autoSendTranscriptToRequester\"]!=undefined) {\n//       update[\"settings.email.autoSendTranscriptToRequester\"] = req.body[\"settings.email.autoSendTranscriptToRequester\"];\n//     }\n//     if (req.body[\"settings.email.notification.conversation.assigned\"]!=undefined) {\n//       update[\"settings.email.notification.conversation.assigned\"] = req.body[\"settings.email.notification.conversation.assigned\"];\n//     }\n//     if (req.body[\"settings.email.notification.conversation.pooled\"]!=undefined) {\n//       update[\"settings.email.notification.conversation.pooled\"] = req.body[\"settings.email.notification.conversation.pooled\"];\n//     }\n//     if (req.body[\"settings.email.templates.assignedRequest\"]!=undefined) {\n//       update[\"settings.email.templates.assignedRequest\"] = req.body[\"settings.email.templates.assignedRequest\"];\n//     }\n//     if (req.body[\"settings.email.templates.assignedEmailMessage\"]!=undefined) {\n//       update[\"settings.email.templates.assignedEmailMessage\"] = req.body[\"settings.email.templates.assignedEmailMessage\"];\n//     }\n//     if (req.body[\"settings.email.templates.pooledRequest\"]!=undefined) {\n//       update[\"settings.email.templates.pooledRequest\"] = req.body[\"settings.email.templates.pooledRequest\"];\n//     }\n//     if (req.body[\"settings.email.templates.pooledEmailMessage\"]!=undefined) {\n//       update[\"settings.email.templates.pooledEmailMessage\"] = req.body[\"settings.email.templates.pooledEmailMessage\"];\n//     }\n//     if (req.body[\"settings.email.templates.newMessage\"]!=undefined) {\n//       update[\"settings.email.templates.newMessage\"] = req.body[\"settings.email.templates.newMessage\"];\n//     }\n//     if (req.body[\"settings.email.templates.newMessageFollower\"]!=undefined) {\n//       update[\"settings.email.templates.newMessageFollower\"] = req.body[\"settings.email.templates.newMessageFollower\"];\n//     }\n//     if (req.body[\"settings.email.templates.ticket\"]!=undefined) {\n//       update[\"settings.email.templates.ticket\"] = req.body[\"settings.email.templates.ticket\"];\n//     }\n//     if (req.body[\"settings.email.templates.sendTranscript\"]!=undefined) {\n//       update[\"settings.email.templates.sendTranscript\"] = req.body[\"settings.email.templates.sendTranscript\"];\n//     }\n//     if (req.body[\"settings.email.templates.emailDirect\"]!=undefined) {\n//       update[\"settings.email.templates.emailDirect\"] = req.body[\"settings.email.templates.emailDirect\"];\n//     }\n//     if (req.body[\"settings.email.from\"]!=undefined) {\n//       update[\"settings.email.from\"] = req.body[\"settings.email.from\"];\n//     }\n//     if (req.body[\"settings.email.config.host\"]!=undefined) {\n//       update[\"settings.email.config.host\"] = req.body[\"settings.email.config.host\"];\n//     }\n//     if (req.body[\"settings.email.config.port\"]!=undefined) {\n//       update[\"settings.email.config.port\"] = req.body[\"settings.email.config.port\"];\n//     }\n//     if (req.body[\"settings.email.config.secure\"]!=undefined) {\n//       update[\"settings.email.config.secure\"] = req.body[\"settings.email.config.secure\"];\n//     }\n//     if (req.body[\"settings.email.config.user\"]!=undefined) {\n//       update[\"settings.email.config.user\"] = req.body[\"settings.email.config.user\"];\n//     }\n//     if (req.body[\"settings.email.config.pass\"]!=undefined) {\n//       update[\"settings.email.config.pass\"] = req.body[\"settings.email.config.pass\"];\n//     }\n//     if (req.body[\"settings.chat_limit_on\"]!=undefined) {\n//       update[\"settings.chat_limit_on\"] = req.body[\"settings.chat_limit_on\"];\n//     }\n//     if (req.body[\"settings.max_agent_assigned_chat\"]!=undefined) {\n//       update[\"settings.max_agent_assigned_chat\"] = req.body[\"settings.max_agent_assigned_chat\"];\n//     }\n//     if (req.body[\"settings.reassignment_on\"]!=undefined) {\n//       update[\"settings.reassignment_on\"] = req.body[\"settings.reassignment_on\"];\n//     }\n//     if (req.body[\"settings.reassignment_delay\"]!=undefined) {\n//       update[\"settings.reassignment_delay\"] = req.body[\"settings.reassignment_delay\"];\n//     }\n//     if (req.body[\"settings.automatic_unavailable_status_on\"]!=undefined) {\n//       update[\"settings.automatic_unavailable_status_on\"] = req.body[\"settings.automatic_unavailable_status_on\"];\n//     }\n//     if (req.body[\"settings.automatic_idle_chats\"]!=undefined) {\n//       update[\"settings.automatic_idle_chats\"] = req.body[\"settings.automatic_idle_chats\"];\n//     }\n\n//     if (req.body.widget!=undefined) {\n//       update.widget = req.body.widget;\n//     }\n//     if (req.body.versions!=undefined) {\n//       update.versions = req.body.versions;\n//     }\n//     if (req.body.channels!=undefined) {\n//       update.channels = req.body.channels; \n//     }\n//     if (req.body.ipFilterEnabled!=undefined) {\n//       update.ipFilterEnabled = req.body.ipFilterEnabled;\n//     }\n//     if (req.body.ipFilter!=undefined) {\n//       update.ipFilter = req.body.ipFilter;\n//     }\n//     if (req.body.ipFilterDenyEnabled!=undefined) {\n//       update.ipFilterDenyEnabled = req.body.ipFilterDenyEnabled;\n//     }\n//     if (req.body.ipFilterDeny!=undefined) {\n//       update.ipFilterDeny = req.body.ipFilterDeny;\n//     }\n//     if (req.body.bannedUsers!=undefined) {\n//       update.bannedUsers = req.body.bannedUsers;\n//     }\n//     if (req.body.profile!=undefined) {\n//       update.profile = req.body.profile;\n//     }\n\n//     winston.debug('UPDATE PROJECT REQ BODY ', update);\n\n//     Project.findByIdAndUpdate(req.params.projectid, update, { new: true, upsert: true }, function (err, updatedProject) {\n//       if (err) {\n//         winston.error('Error putting project ', err);\n//         return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//       }\n//       projectEvent.emit('project.update', updatedProject );\n//       res.json(updatedProject);\n//     });\n  \n//   } catch (err) {\n//     winston.warn(\"Profile modification: permission denied.\");\n//     res.status(403).send({ success: false, error: \"You don't have the permission required to modify the project profile\"});\n//   }\n\n// })\n\nrouter.put('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  \n  winston.debug('UPDATE PROJECT REQ BODY ', req.body);\n\n  var update = {};\n  let updating_quotes = false;\n\n  if (req.body.profile) {\n\n    if (req.user &&\n        req.user.attributes &&\n        req.user.attributes.isSuperadmin === true) {\n          \n          winston.debug(\"Superadmin can modify the project profile\")\n          update.profile = req.body.profile;\n          if (req.body.profile.quotes) {\n            updating_quotes = true;\n          }\n\n          /**\n           * Possibile Miglioramento\n           * Eliminare solo le chiavi di redis di notify solo per le quote che si stanno modificando.\n           * Per farlo è necessario permettere la modifica puntuale del project profile, attualmente non disponibile.\n           */\n\n          delete req.user.attributes.isSuperadmin;\n    }\n    else {\n      winston.verbose(\"Project profile can't be modified by the current user \" + req.user._id);\n      return res.status(403).send({ success: false,  error: \"You don't have the permission required to modify the project profile\"});\n    }\n\n    // check if super admin\n    // let token = req.headers.authorization\n    // token = token.split(\" \")[1];\n\n    // let decoded = jwt.verify(token, configSecret);\n    // winston.debug(\"user decoded: \", decoded);\n    // console.log(\"user decoded: \", decoded);\n\n    // if (!process.env.ADMIN_EMAIL) {\n    //   winston.warn(\"Missing admin email parameter in environment\");\n    //   return res.status(401).send({ success: false, error: \"Missing admin email parameter\"});\n    // }\n\n    // if (!decoded) {\n    //   winston.warn(\"Profile modification: permission denied.\");\n    //   return res.status(403).send({ success: false, error: \"You don't have the permission required to modify the project profile. Can't decode user.\"});\n    // }\n\n    // if (decoded.email !== process.env.ADMIN_EMAIL) {\n    //   winston.warn(\"Profile modification: permission denied.\");\n    //   return res.status(403).send({ success: false, error: \"You don't have the permission required to modify the project profile\"});\n    // }\n\n    // console.log(\"You can modify the project profile\");\n\n    // winston.info(\"Illegal field profile detected. Deny project profile update.\");\n    // return res.status(403).send({ success: false,  error: \"You cannot edit the project profile.\"});\n  }\n  \n//like patch\n  if (req.body.name!=undefined) {\n    update.name = req.body.name;\n  }\n\n  if (req.body.activeOperatingHours!=undefined) {\n    update.activeOperatingHours = req.body.activeOperatingHours;\n  }\n  \n  if (req.body.operatingHours!=undefined) {\n    update.operatingHours = req.body.operatingHours;\n  }\n\n  if (req.body.timeSlots!=undefined) {\n    update.timeSlots = req.body.timeSlots;\n  }\n  \n  if (req.body.settings!=undefined) {\n    update.settings = req.body.settings;\n  }\n  \n  if (req.body[\"settings.email.autoSendTranscriptToRequester\"]!=undefined) {\n    update[\"settings.email.autoSendTranscriptToRequester\"] = req.body[\"settings.email.autoSendTranscriptToRequester\"];\n  }\n\n  if (req.body[\"settings.email.notification.conversation.assigned\"]!=undefined) {\n    update[\"settings.email.notification.conversation.assigned\"] = req.body[\"settings.email.notification.conversation.assigned\"];\n  }\n\n  if (req.body[\"settings.email.notification.conversation.pooled\"]!=undefined) {\n    update[\"settings.email.notification.conversation.pooled\"] = req.body[\"settings.email.notification.conversation.pooled\"];\n  }\n\n  if (req.body[\"settings.email.templates.assignedRequest\"]!=undefined) {\n    update[\"settings.email.templates.assignedRequest\"] = req.body[\"settings.email.templates.assignedRequest\"];\n  }\n  if (req.body[\"settings.email.templates.assignedEmailMessage\"]!=undefined) {\n    update[\"settings.email.templates.assignedEmailMessage\"] = req.body[\"settings.email.templates.assignedEmailMessage\"];\n  }\n  if (req.body[\"settings.email.templates.pooledRequest\"]!=undefined) {\n    update[\"settings.email.templates.pooledRequest\"] = req.body[\"settings.email.templates.pooledRequest\"];\n  }\n  if (req.body[\"settings.email.templates.pooledEmailMessage\"]!=undefined) {\n    update[\"settings.email.templates.pooledEmailMessage\"] = req.body[\"settings.email.templates.pooledEmailMessage\"];\n  }\n  if (req.body[\"settings.email.templates.newMessage\"]!=undefined) {\n    update[\"settings.email.templates.newMessage\"] = req.body[\"settings.email.templates.newMessage\"];\n  }\n  if (req.body[\"settings.email.templates.newMessageFollower\"]!=undefined) {\n    update[\"settings.email.templates.newMessageFollower\"] = req.body[\"settings.email.templates.newMessageFollower\"];\n  }\n  if (req.body[\"settings.email.templates.ticket\"]!=undefined) {\n    update[\"settings.email.templates.ticket\"] = req.body[\"settings.email.templates.ticket\"];\n  }\n  if (req.body[\"settings.email.templates.sendTranscript\"]!=undefined) {\n    update[\"settings.email.templates.sendTranscript\"] = req.body[\"settings.email.templates.sendTranscript\"];\n  }\n  if (req.body[\"settings.email.templates.emailDirect\"]!=undefined) {\n    update[\"settings.email.templates.emailDirect\"] = req.body[\"settings.email.templates.emailDirect\"];\n  }\n\n  if (req.body[\"settings.email.from\"]!=undefined) {\n    update[\"settings.email.from\"] = req.body[\"settings.email.from\"];\n  }\n  if (req.body[\"settings.email.config.host\"]!=undefined) {\n    update[\"settings.email.config.host\"] = req.body[\"settings.email.config.host\"];\n  }\n  if (req.body[\"settings.email.config.port\"]!=undefined) {\n    update[\"settings.email.config.port\"] = req.body[\"settings.email.config.port\"];\n  }\n  if (req.body[\"settings.email.config.secure\"]!=undefined) {\n    update[\"settings.email.config.secure\"] = req.body[\"settings.email.config.secure\"];\n  }\n  if (req.body[\"settings.email.config.user\"]!=undefined) {\n    update[\"settings.email.config.user\"] = req.body[\"settings.email.config.user\"];\n  }\n  if (req.body[\"settings.email.config.pass\"]!=undefined) {\n    update[\"settings.email.config.pass\"] = req.body[\"settings.email.config.pass\"];\n  }\n\n\n  \n  /*\n\n  if (req.body.settings.email.templates.assignedRequest!=undefined) {\n    // if (req.body[\"settings.email.templates.assignedRequest.html\"]!=undefined) {\n    console.log(\"assignedRequest\");\n    update[\"settings.email.templates.assignedRequest\"] = req.body.settings.email.templates.assignedRequest;\n  }\n  if (req.body[\"settings.email.templates.assignedEmailMessage.html\"]!=undefined) {\n    update[\"settings.email.templates.assignedEmailMessage.html\"] = req.body[\"settings.email.templates.assignedEmailMessage.html\"];\n  }\n  if (req.body.settings.email.templates.pooledRequest!=undefined) {\n    console.log(\"pooledRequest\");\n    update[\"settings.email.templates.pooledRequest\"] = req.body.settings.email.templates.pooledRequest;\n  }\n*/\n\n  if (req.body[\"settings.chat_limit_on\"]!=undefined) {\n    update[\"settings.chat_limit_on\"] = req.body[\"settings.chat_limit_on\"];\n  }\n\n  if (req.body[\"settings.max_agent_assigned_chat\"]!=undefined) {\n    update[\"settings.max_agent_assigned_chat\"] = req.body[\"settings.max_agent_assigned_chat\"];\n  }\n\n\n\n  if (req.body[\"settings.reassignment_on\"]!=undefined) {\n    update[\"settings.reassignment_on\"] = req.body[\"settings.reassignment_on\"];\n  }\n\n  if (req.body[\"settings.reassignment_delay\"]!=undefined) {\n    update[\"settings.reassignment_delay\"] = req.body[\"settings.reassignment_delay\"];\n  }\n\n\n  if (req.body[\"settings.automatic_unavailable_status_on\"]!=undefined) {\n    update[\"settings.automatic_unavailable_status_on\"] = req.body[\"settings.automatic_unavailable_status_on\"];\n  }\n\n  if (req.body[\"settings.automatic_idle_chats\"]!=undefined) {\n    update[\"settings.automatic_idle_chats\"] = req.body[\"settings.automatic_idle_chats\"];\n  }\n\n  if (req.body[\"settings.current_agent_my_chats_only\"]!=undefined) {\n    update[\"settings.current_agent_my_chats_only\"] = req.body[\"settings.current_agent_my_chats_only\"];\n  }\n\n  if (req.body[\"settings.chatbots_attributes_hidden\"]!=undefined) {\n    update[\"settings.chatbots_attributes_hidden\"] = req.body[\"settings.chatbots_attributes_hidden\"];\n  }\n\n  if (req.body[\"settings.allow_send_emoji\"]!=undefined) {\n    update[\"settings.allow_send_emoji\"] = req.body[\"settings.allow_send_emoji\"];\n  }\n\n  if (req.body[\"settings.allowed_urls\"]!=undefined) {\n    update[\"settings.allowed_urls\"] = req.body[\"settings.allowed_urls\"];\n  }\n\n  if (req.body[\"settings.allowed_urls_list\"]!=undefined) {\n    update[\"settings.allowed_urls_list\"] = req.body[\"settings.allowed_urls_list\"];\n  }\n\n  if (req.body[\"settings.allowed_upload_extentions\"]!=undefined) {\n    update[\"settings.allowed_upload_extentions\"] = req.body[\"settings.allowed_upload_extentions\"];\n  }\n  \n  if (req.body.widget!=undefined) {\n    update.widget = req.body.widget;\n  }\n\n  if (req.body.versions!=undefined) {\n    update.versions = req.body.versions;\n  }\n  \n  if (req.body.channels!=undefined) {\n    update.channels = req.body.channels; \n  }\n\n  if (req.body.ipFilterEnabled!=undefined) {\n    update.ipFilterEnabled = req.body.ipFilterEnabled;\n  }\n\n  if (req.body.ipFilter!=undefined) {\n    update.ipFilter = req.body.ipFilter;\n  }\n\n  if (req.body.ipFilterDenyEnabled!=undefined) {\n    update.ipFilterDenyEnabled = req.body.ipFilterDenyEnabled;\n  }\n\n  if (req.body.ipFilterDeny!=undefined) {\n    update.ipFilterDeny = req.body.ipFilterDeny;\n  }\n\n  if (req.body.bannedUsers!=undefined) {\n    update.bannedUsers = req.body.bannedUsers;\n  }\n  \n  // if (req.body.defaultLanguage!=undefined) {\n  //   update.defaultLanguage = req.body.defaultLanguage; \n  // }\n\n  \n  winston.debug('UPDATE PROJECT REQ BODY ', update);\n  // console.log(\"update\",JSON.stringify(update));\n\n  Project.findByIdAndUpdate(req.params.projectid, update, { new: true, upsert: true }, function (err, updatedProject) {\n    if (err) {\n      winston.error('Error putting project ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n    projectEvent.emit('project.update', updatedProject );\n\n    if (updating_quotes == true) {\n      let obj = { createdAt: new Date() };\n      let quoteManager = req.app.get('quote_manager');\n      quoteManager.invalidateCheckpointKeys(updatedProject, obj);\n    }\n    res.json(updatedProject);\n  });\n});\n\nrouter.patch('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  winston.debug('PATCH PROJECT REQ BODY ', req.body);\n\n  var update = {};\n  \n  if (req.body.name!=undefined) {\n    update.name = req.body.name;\n  }\n\n  if (req.body.activeOperatingHours!=undefined) {\n    update.activeOperatingHours = req.body.activeOperatingHours;\n  }\n  \n  if (req.body.operatingHours!=undefined) {\n    update.operatingHours = req.body.operatingHours;\n  }\n  \n  if (req.body.settings!=undefined) {\n    update.settings = req.body.settings;\n  }\n  \n  if (req.body[\"settings.email.autoSendTranscriptToRequester\"]!=undefined) {\n    update[\"settings.email.autoSendTranscriptToRequester\"] = req.body[\"settings.email.autoSendTranscriptToRequester\"];\n  }\n\n\n  if (req.body[\"settings.email.notification.conversation.assigned\"]!=undefined) {\n    update[\"settings.email.notification.conversation.assigned\"] = req.body[\"settings.email.notification.conversation.assigned\"];\n  }\n\n  if (req.body[\"settings.email.notification.conversation.pooled\"]!=undefined) {\n    update[\"settings.email.notification.conversation.pooled\"] = req.body[\"settings.email.notification.conversation.pooled\"];\n  }\n\n\n  \n  if (req.body[\"settings.email.templates.assignedRequest\"]!=undefined) {\n    update[\"settings.email.templates.assignedRequest\"] = req.body[\"settings.email.templates.assignedRequest\"];\n  }\n  if (req.body[\"settings.email.templates.assignedEmailMessage\"]!=undefined) {\n    update[\"settings.email.templates.assignedEmailMessage\"] = req.body[\"settings.email.templates.assignedEmailMessage\"];\n  }\n  if (req.body[\"settings.email.templates.pooledRequest\"]!=undefined) {\n    update[\"settings.email.templates.pooledRequest\"] = req.body[\"settings.email.templates.pooledRequest\"];\n  }\n  if (req.body[\"settings.email.templates.pooledEmailMessage\"]!=undefined) {\n    update[\"settings.email.templates.pooledEmailMessage\"] = req.body[\"settings.email.templates.pooledEmailMessage\"];\n  }\n  if (req.body[\"settings.email.templates.newMessage\"]!=undefined) {\n    update[\"settings.email.templates.newMessage\"] = req.body[\"settings.email.templates.newMessage\"];\n  }\n  if (req.body[\"settings.email.templates.ticket\"]!=undefined) {\n    update[\"settings.email.templates.ticket\"] = req.body[\"settings.email.templates.ticket\"];\n  }\n  if (req.body[\"settings.email.templates.sendTranscript\"]!=undefined) {\n    update[\"settings.email.templates.sendTranscript\"] = req.body[\"settings.email.templates.sendTranscript\"];\n  }\n\n\n  if (req.body[\"settings.email.from\"]!=undefined) {\n    update[\"settings.email.from\"] = req.body[\"settings.email.from\"];\n  }\n  if (req.body[\"settings.email.config.host\"]!=undefined) {\n    update[\"settings.email.config.host\"] = req.body[\"settings.email.config.host\"];\n  }\n  if (req.body[\"settings.email.config.port\"]!=undefined) {\n    update[\"settings.email.config.port\"] = req.body[\"settings.email.config.port\"];\n  }\n  if (req.body[\"settings.email.config.secure\"]!=undefined) {\n    update[\"settings.email.config.secure\"] = req.body[\"settings.email.config.secure\"];\n  }\n  if (req.body[\"settings.email.config.user\"]!=undefined) {\n    update[\"settings.email.config.user\"] = req.body[\"settings.email.config.user\"];\n  }\n  if (req.body[\"settings.email.config.pass\"]!=undefined) {\n    update[\"settings.email.config.pass\"] = req.body[\"settings.email.config.pass\"];\n  }\n\n\n  if (req.body[\"settings.chat_limit_on\"]!=undefined) {\n    update[\"settings.chat_limit_on\"] = req.body[\"settings.chat_limit_on\"];\n  }\n\n  if (req.body[\"settings.max_agent_assigned_chat\"]!=undefined) {\n    update[\"settings.max_agent_assigned_chat\"] = req.body[\"settings.max_agent_assigned_chat\"];\n  }\n\n\n\n  if (req.body[\"settings.reassignment_on\"]!=undefined) {\n    update[\"settings.reassignment_on\"] = req.body[\"settings.reassignment_on\"];\n  }\n\n  if (req.body[\"settings.reassignment_delay\"]!=undefined) {\n    update[\"settings.reassignment_delay\"] = req.body[\"settings.reassignment_delay\"];\n  }\n\n\n\n\n  if (req.body[\"settings.automatic_unavailable_status_on\"]!=undefined) {\n    update[\"settings.automatic_unavailable_status_on\"] = req.body[\"settings.automatic_unavailable_status_on\"];\n  }\n\n  if (req.body[\"settings.automatic_idle_chats\"]!=undefined) {\n    update[\"settings.automatic_idle_chats\"] = req.body[\"settings.automatic_idle_chats\"];\n  }\n  \n  if (req.body.widget!=undefined) {\n    update.widget = req.body.widget;\n  }\n\n  if (req.body.versions!=undefined) {\n    update.versions = req.body.versions;\n  }\n  \n  if (req.body.channels!=undefined) {\n    update.channels = req.body.channels; \n  }\n  \n  if (req.body.ipFilterEnabled!=undefined) {\n    update.ipFilterEnabled = req.body.ipFilterEnabled;\n  }\n\n  if (req.body.ipFilter!=undefined) {\n    update.ipFilter = req.body.ipFilter;\n  }\n\n  if (req.body.ipFilterDenyEnabled!=undefined) {\n    update.ipFilterDenyEnabled = req.body.ipFilterDenyEnabled;\n  }\n\n  if (req.body.ipFilterDeny!=undefined) {\n    update.ipFilterDeny = req.body.ipFilterDeny;\n  }\n\n  if (req.body.bannedUsers!=undefined) {\n    update.bannedUsers = req.body.bannedUsers;\n  }\n  \n    \n  // if (req.body.defaultLanguage!=undefined) {\n  //   update.defaultLanguage = req.body.defaultLanguage; \n  // }\n\n \n  winston.debug('UPDATE PROJECT REQ BODY ', update);\n\n  Project.findByIdAndUpdate(req.params.projectid, update, { new: true, upsert: true }, function (err, updatedProject) {\n    if (err) {\n      winston.error('Error putting project ', err);\n      return res.status(500).send({ success: false, msg: 'Error patching object.' });\n    }\n    projectEvent.emit('project.update', updatedProject );\n    res.json(updatedProject);\n  });\n});\n\nrouter.patch('/:projectid/attributes', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  var data = req.body;\n\n  // TODO use service method\n\n  Project.findById(req.params.projectid, function (err, updatedProject) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n     if (!updatedProject) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      \n      if (!updatedProject.attributes) {\n        winston.debug(\"empty attributes\")\n        updatedProject.attributes = {};\n      }\n\n      winston.debug(\" updatedProject attributes\", updatedProject.attributes)\n        \n        Object.keys(data).forEach(function(key) {\n          var val = data[key];\n          winston.debug(\"data attributes \"+key+\" \" +val)\n          updatedProject.attributes[key] = val;\n        });     \n        \n        winston.debug(\" updatedProject attributes\", updatedProject.attributes)\n\n        // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n        updatedProject.markModified('attributes');\n\n          //cacheinvalidation\n          updatedProject.save(function (err, savedProject) {\n          if (err) {\n            winston.error(\"error saving project attributes\",err)\n            return res.status(500).send({ success: false, msg: 'Error getting object.' });\n          }\n          winston.verbose(\" saved project attributes\",savedProject.toObject())\n          projectEvent.emit('project.update', savedProject);\n\n            res.json(savedProject);\n        });\n  });\n  \n});\n\n\nrouter.post('/:projectid/ban', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  winston.debug('PATCH PROJECT REQ BODY ', req.body);\n\n  var ban = {};\n  ban.id = req.body.id;\n  ban.ip = req.body.ip;\n\n  Project.findByIdAndUpdate(req.params.projectid, { $push: { bannedUsers: ban } }, { new: true, upsert: false }, function (err, updatedProject) {\n    if (err) {\n      winston.error('Error putting project ', err);\n      return res.status(500).send({ success: false, msg: 'Error patching object.' });\n    }\n    projectEvent.emit('project.update', updatedProject );\n    projectEvent.emit('project.update.user.ban', {banInfo: ban, project: updatedProject });\n    res.json(updatedProject);\n  });\n\n});\nrouter.delete('/:projectid/ban/:banid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  \n  // winston.info('quiiiiii');\n  //cacheinvalidation\n\n  \n  // devi prendere id utente prima di eliminarlo\nProject.findByIdAndUpdate(req.params.projectid, { $pull: { bannedUsers: { \"_id\": req.params.banid }}}, { new: true, upsert: false }, function (err, updatedProject) {\n  if (err) {\n    winston.error('Error putting project ', err);\n    return res.status(500).send({ success: false, msg: 'Error patching object.' });\n  }\n  projectEvent.emit('project.update', updatedProject );\n  projectEvent.emit('project.update.user.unban', {banInfo: req.params.banid, project: updatedProject});\n  res.json(updatedProject);\n});\n\n});\n\nrouter.get('/all', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], function (req, res) {\n\n  if (req.headers.authorization) {\n\n    let token = req.headers.authorization.split(\" \")[1];\n    let decode = jwt.verify(token, pubConfigSecret)\n    if (decode && (decode.email === process.env.ADMIN_EMAIL)) {\n\n      Project.aggregate([\n        // {\n        //   $match: {\n        //     status: 100,\n        //     //createdAt: { $gte: startDate}\n        //   },\n        // },\n        {\n          $sort: {\n            createdAt: -1\n          },\n        },\n        {\n          $lookup: {\n            from: 'project_users',          \n            localField: '_id',              \n            foreignField: 'id_project',     \n            as: 'project_user',            \n            pipeline: [                     \n              { $match: { role: 'owner' } } \n            ]\n          }\n        },\n        {\n          $addFields: {\n            project_user: { $arrayElemAt: ['$project_user', 0] }\n          }\n        },\n        {\n          $lookup: {\n            from: 'users',\n            localField: 'project_user.id_user',              \n            foreignField: '_id',     \n            as: 'user'            \n          },\n        },\n        {\n          $addFields: {\n            user: { $arrayElemAt: ['$user', 0] } \n          }\n        }\n      ])\n        .then((projects) => {\n          winston.verbose(\"Projects found \" + projects.length)\n          // const fieldsToKeep = ['_id', 'name', 'createdBy', 'createdAt', 'user.email' ];\n\n          const filteredProjects = projects.map(project => {\n            const filteredProject = {};\n            filteredProject._id = project._id;\n            filteredProject.name = project.name;\n            filteredProject.owner = project.user?.email;\n            filteredProject.createdAt = project.createdAt;\n            filteredProject.profile_name = project.profile?.name;\n            // ... add other fields here\n\n            // fieldsToKeep.forEach(field => {\n            //   if (project[field] !== undefined) {\n            //     filteredProject[field] = project[field];\n            //   }\n            // });\n            return filteredProject;\n          });\n\n          return res.status(200).send(filteredProjects);\n        })\n        .catch((err) => {\n          console.error(err);\n          return res.status(500).send({ success: false,  error: err});\n        });\n      \n      // let updatedUser = await User.findByIdAndUpdate(savedUser._id, { emailverified: true }, { new: true }).exec();\n      // winston.debug(\"updatedUser: \", updatedUser);\n      // skipVerificationEmail = true;\n      // winston.verbose(\"skip sending verification email\")\n    } else {\n      return res.status(403).send({ success: false,  error: \"You don't have the permission required to perform the operation\"});\n    }\n\n  }\n\n});\n\n\n//roleChecker.hasRole('agent') works because req.params.projectid is valid using :projectid of this method\nrouter.get('/:projectid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], function (req, res) {\n  winston.debug(req.body);\n  let q = Project.findOne({_id: req.params.projectid, status:100});\n  if (cacheEnabler.project) { \n    q.cache(cacheUtil.longTTL, \"projects:id:\"+req.params.projectid)  //project_cache\n    winston.debug('project cache enabled for /project detail');\n  }\n  q.exec(function (err, project) {\n    if (err) {\n      winston.error('Error getting project ', err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!project) {\n      winston.warn('Project not found ');\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n\n     //TODO REMOVE settings from project\n    res.json(project);\n  });\n});\n\n// GET ALL PROJECTS BY CURRENT USER ID\n// router.get('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], function (req, res) {\n  // altrimenti 403\nrouter.get('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], function (req, res) {\n  winston.debug('REQ USER ID ', req.user._id);\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  } \n  winston.debug(\"direction\",direction);\n\n  var sortField = \"updatedAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  } \n  winston.debug(\"sortField\",sortField);\n\n  var sortQuery={};\n  sortQuery[sortField] = direction;\n\n// rolequery\n\n  Project_user.find({ id_user: req.user._id , roleType: RoleConstants.TYPE_AGENTS, status: \"active\"}).\n  // Project_user.find({ id_user: req.user._id , role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\"}).\n    // populate('id_project').\n    populate({\n      path: 'id_project',\n      // match: { status: 100 }, //not filter only not populate\n    }).\n    sort(sortQuery).\n    exec(function (err, project_users) {\n      if (err) {\n        winston.error('Error getting project_users: ', err);\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }       \n\n\n\n      //organization: if third sub domain iterate and put only project with organization==subdomain otherwise remove projects with org\n      winston.debug(\"orgUtil.ORGANIZATION_ENABLED: \" + orgUtil.ORGANIZATION_ENABLED);\n      if (orgUtil.ORGANIZATION_ENABLED == true ) {\n\n              // winston.info(\"project_users\", project_users);\n        winston.debug(\"project_users.length:\"+ project_users.length);\n\n        let org = orgUtil.getOrg(req);\n        winston.debug(\"org:\"+ org);\n        \n        if (org!=undefined) {\n          winston.debug(\"org!=undefined\");\n\n          var project_users = project_users.filter(function (projectUser) {\n            if (projectUser.id_project.organization && projectUser.id_project.organization === org ) {\n              winston.debug(\"keep\");\n              return true;\n            }\n          });\n\n        /* for (var i = 0; i < project_users.length; i++) { \n            winston.info(\"project_users[i].id_project.organization: \" + project_users[i].id_project.organization);\n            if (project_users[i].id_project.organization && project_users[i].id_project.organization === org ) {\n              //keep\n              winston.info(\"keep\");\n            } else {\n              // project_users.splice(i, 1); // 2nd parameter means remove one item only\n              winston.info(\"splice\");\n            }\n          }*/\n        } else {\n\n            var project_users = project_users.filter(function (projectUser) {\n              if (projectUser.id_project.organization == undefined ) {\n                // winston.info(\"keep\");\n                return true;\n              }\n            });        \n          \n\n          /*\n          for (var i = 0; i < project_users.length; i++) { \n            winston.info(\"project_users[i].id_project.organization: \" + project_users[i].id_project.organization);\n            if (project_users[i].id_project.organization) {\n              project_users.splice(i, 1); // 2nd parameter means remove one item only\n            }\n          }*/\n        }\n      } else {\n        winston.debug(\"no\")\n      }\n\n\n      project_users.sort((a, b) => (a.id_project && b.id_project && a.id_project.updatedAt > b.id_project.updatedAt) ? 1 : -1)\n      project_users.reverse(); \n\n      //TODO REMOVE settings from project\n      res.json(project_users);\n    });\n});\n\n// GET ALL PROJECTS BY CURRENT USER ID. usaed by unisalento to know if a project is open \nrouter.get('/:projectid/isopen', function (req, res) {\n\n  let project_id = req.params.projectid;\n  // Check if a timeSlot is passed\n  if (req.query.timeSlot) {\n    let slot_id = req.query.timeSlot;\n    operatingHoursService.slotIsOpenNow(project_id, slot_id, (isOpen, err) => {\n\n      if (err) {\n        winston.error(\"Error getting slotIsOpenNow \", err);\n        return res.status(500).send({ success: false, error: err });\n      }\n      return res.status(200).send({ isopen: isOpen})\n    })\n\n  } else {\n\n    operatingHoursService.projectIsOpenNow(project_id, function (isOpen, err) {\n     winston.debug('project', project_id, 'isopen: ', isOpen);\n  \n      if (err) {\n        winston.error('Error getting projectIsOpenNow', err);\n        return res.status(500).send({ success: false, msg: err });\n      } \n      return res.status(200).send({ isopen: isOpen})\n   });\n  }\n});\n\n\nrouter.get('/:projectid/users/availables', async  (req, res) => {\n  \n  let projectid = req.params.projectid;\n  let raw_option = req.query.raw;\n  let dep_id = req.query.department;\n  let isOpen = true;\n\n  winston.debug(\"(Users Availables) raw_option: \" + raw_option);\n  winston.debug(\"(Users Availables) dep_id: \" + dep_id);\n\n  let available_agents_array = [];\n\n  if (!raw_option || raw_option === false) {\n    try {\n      isOpen = await new Promise((resolve, reject) => {\n        operatingHoursService.projectIsOpenNow(projectid, (isOpen, err) => {\n          if (err) reject(err);\n          else resolve(isOpen);\n        });\n      });\n    } catch (err) {\n      winston.error(\"(Users Availables) check operating hours error: \", err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n  }\n\n  if (isOpen === false) {\n    return res.json(available_agents_array);\n  }\n// rolequery\n  let query = { id_project: projectid, user_available: true, roleType: RoleConstants.TYPE_AGENTS };\n  // let query = { id_project: projectid, user_available: true, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]} };\n\n\n  if (dep_id) {\n    let department = await Department.findById(dep_id).catch((err) => {\n      winston.error(\"(Users Availables) find department error: \", err)\n      return res.status(500).send({ success: false, error: err })\n    })\n\n    if (!department) {\n      winston.error(\"(Users Availables) department not found\")\n      return res.status(404).send({ success: false, error: \"Department \" + dep_id + \" not found\" })\n    }\n\n    let group_id = department.id_group;\n    let groups = department.groups;\n\n    if (groups && Array.isArray(groups) && groups.length > 0) {\n      // Converti i group_id in ObjectId per la query\n      const groupIds = groups\n        .map(g => g.group_id)\n        .filter(id => !!id)\n        .map(id => mongoose.Types.ObjectId(id));\n    \n      if (groupIds.length > 0) {\n        const dbGroups = await Group.find({ _id: { $in: groupIds } }).catch((err) => {\n          winston.error(\"(Users Availables) find groups error: \", err);\n          return res.status(500).send({ success: false, error: err });\n        });\n    \n        if (!dbGroups || dbGroups.length === 0) {\n          winston.error(\"(Users Availables) no valid groups found\");\n          return res.status(404).send({ success: false, error: \"No valid groups found\" });\n        }\n    \n        // Filtra i gruppi abilitati\n        const enabledGroups = dbGroups.filter(g => g.enabled !== false);\n    \n        if (enabledGroups.length === 0) {\n          winston.error(\"(Users Availables) all groups are disabled\");\n          return res.status(403).send({ success: false, error: \"All groups are currently disabled\" });\n        }\n    \n        // Raccogli tutti i membri (stringhe) e rimuovi duplicati\n        const members = [...new Set(enabledGroups.flatMap(g => g.members))];\n    \n        query.id_user = { $in: members };\n      }\n    }\n    else if (group_id) {\n      let group = await Group.findById(group_id).catch((err) => {\n        winston.error(\"(Users Availables) find group error: \", err)\n        return res.status(500).send({ success: false, error: err })\n      })\n\n      if (!group) {\n        winston.error(\"(Users Availables) group not found\")\n        return res.status(404).send({ success: false, error: \"Group \" + group_id + \" not found\" })\n      }\n\n      if (group.enabled === false) {\n        winston.error(\"(Users Availables) group disabled\")\n        return res.status(403).send({ success: false, error: \"Group \" + group_id + \" is currently disabled\" })\n      }\n\n      query.id_user = { $in: group.members.map(id => mongoose.Types.ObjectId(id) )}\n    }\n  } \n  \n  winston.debug(\"(Users Availables) project_users query \", query)\n  Project_user.find(query)\n      .populate('id_user')\n      .exec( async (err, project_users) => {\n        if (err) {\n          winston.debug('PROJECT ROUTES - FINDS AVAILABLES project_users - ERROR: ', err);\n          return res.status(500).send({ success: false, msg: 'Error getting object.' + err});\n        }\n\n        let project = await Project.findById(projectid).catch((err) => {\n          winston.error(\"find project error: \", err)\n          res.status(500).send({ success: false, error: err })\n        })\n\n        // check on SMART ASSIGNMENT\n        let available_agents = projectUserService.checkAgentsAvailablesWithSmartAssignment(project, project_users);\n        winston.verbose(\"(Users Availables) available agents after smart assignment check\", available_agents);\n\n        if (available_agents) {\n  \n          available_agents_array = [];\n          available_agents.forEach(agent => {\n            //console.log(\"agent: \", agent);\n            if (agent.id_user) {\n              available_agents_array.push({ \n                \"id\": agent.id_user._id, \n                \"pu_id\": agent._id,\n                \"fullname\": agent.id_user.firstname + \" \" + agent.id_user.lastname,\n                \"email\": agent.id_user.email,\n                \"assigned_request\": agent.number_assigned_requests\n              });\n            } else {\n              winston.warn(\"(Users Availables) agent.id_user is undefined\");\n            }\n          });\n\n          winston.debug(\"(Users Availables) return following available_agents_array\", available_agents_array);\n          res.json(available_agents_array);\n        }\n      })\n  \n})\n\n// OLD ENDPOINT for /users/availables\n//togli questo route da qui e mettilo in altra route\n// NEW -  RETURN  THE USER NAME AND THE USER ID OF THE AVAILABLE PROJECT-USER FOR THE PROJECT ID PASSED\n// router.get('/:projectid/users/availables', function (req, res) {\n//   //winston.debug(\"PROJECT ROUTES FINDS AVAILABLES project_users: projectid\", req.params.projectid);\n\n//   if (req.query.raw && (req.query.raw === true || req.query.raw === 'true')) {\n//     Project_user.find({ id_project: req.params.projectid, user_available: true, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}}).\n//           populate('id_user').\n//           exec(function (err, project_users) {\n//             if (err) {\n//               winston.debug('PROJECT ROUTES - FINDS AVAILABLES project_users - ERROR: ', err);\n//               return res.status(500).send({ success: false, msg: 'Error getting object.' });\n//             }\n//             if (project_users) {\n  \n//               user_available_array = [];\n//               project_users.forEach(project_user => {\n//                 if (project_user.id_user) {\n//                   // winston.debug('PROJECT ROUTES - AVAILABLES PROJECT-USER: ', project_user)\n//                   user_available_array.push({ \"id\": project_user.id_user._id, \"firstname\": project_user.id_user.firstname });\n//                 } else {\n//                   // winston.debug('PROJECT ROUTES - AVAILABLES PROJECT-USER (else): ', project_user)\n//                 }\n//               });\n  \n//               //winston.debug('ARRAY OF THE AVAILABLE USER ', user_available_array);\n//               res.json(user_available_array);\n//             }\n//           });\n//   } else {\n//     operatingHoursService.projectIsOpenNow(req.params.projectid, function (isOpen, err) {\n//       //winston.debug('P ---> [ OHS ] -> [ PROJECT ROUTES ] -> IS OPEN THE PROJECT: ', isOpen);\n  \n//       if (err) {\n//         winston.debug('P ---> [ OHS ] -> [ PROJECT ROUTES ] -> IS OPEN THE PROJECT - EROR: ', err)\n//         // sendError(err, res);\n//         return res.status(500).send({ success: false, msg: err });\n//       } else if (isOpen) {\n  \n//         Project_user.find({ id_project: req.params.projectid, user_available: true, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}}).\n//           populate('id_user').\n//           exec(function (err, project_users) {\n//             if (err) {\n//               winston.debug('PROJECT ROUTES - FINDS AVAILABLES project_users - ERROR: ', err);\n//               return res.status(500).send({ success: false, msg: 'Error getting object.' });\n//             }\n//             if (project_users) {\n  \n//               user_available_array = [];\n//               project_users.forEach(project_user => {\n//                 if (project_user.id_user) {\n//                   // winston.debug('PROJECT ROUTES - AVAILABLES PROJECT-USER: ', project_user)\n//                   user_available_array.push({ \"id\": project_user.id_user._id, \"firstname\": project_user.id_user.firstname });\n//                 } else {\n//                   // winston.debug('PROJECT ROUTES - AVAILABLES PROJECT-USER (else): ', project_user)\n//                 }\n//               });\n  \n//               //winston.debug('ARRAY OF THE AVAILABLE USER ', user_available_array);\n  \n//               res.json(user_available_array);\n//             }\n//           });\n  \n  \n//       } else {\n//        // winston.debug('P ---> [ OHS ] -> [ PROJECT ROUTES ] -> IS OPEN THE PRJCT: ', isOpen, ' -> AVAILABLE EMPTY');\n//         // closed\n//         user_available_array = [];\n//         res.json(user_available_array);\n//       }\n//     });\n//   }\n\n  \n\n// });\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/project_user.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\nvar Project_user = require(\"../models/project_user\");\nvar mongoose = require('mongoose');\nvar User = require(\"../models/user\");\nvar emailService = require(\"../services/emailService\");\nvar Project = require(\"../models/project\");\nvar pendinginvitation = require(\"../services/pendingInvitationService\");\nconst authEvent = require('../event/authEvent');\nvar winston = require('../config/winston');\nvar RoleConstants = require(\"../models/roleConstants\");\nvar ProjectUserUtil = require(\"../utils/project_userUtil\");\nconst uuidv4 = require('uuid/v4');\n\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role');\nconst puEvent = require('../event/projectUserEvent');\n\n\nrouter.post('/invite', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n  winston.debug('Invite ProjectUser body ', req.body);\n\n  let id_project = req.projectid;\n  let email = req.body.email;\n  if (email) {\n    email = email.toLowerCase();\n  }\n\n  winston.debug('Invite ProjectUser with email ' + email + ' on project ' + req.projectid);\n  \n  User.findOne({ email: email, status: 100 }, (err, user) => {\n    \n    if (err) {\n      winston.error(\"Error in searching for a possible existing project user with email \" + email);\n      return res.status(500).send({ success: false, error: \"An error occurred during the invite process\" });\n    }\n\n    if (!user) {\n      // User not registered on Tiledesk Platform -> Save email and project_id in pending invitation\n      // TODO req.user.firstname is null for bot visitor\n      return pendinginvitation.saveInPendingInvitation(req.projectid, req.project.name, email, req.body.role, req.user._id, req.user.firstname, req.user.lastname).then(function (savedPendingInvitation) {\n        var eventData = { req: req, savedPendingInvitation: savedPendingInvitation };\n        winston.debug(\"eventData\", eventData);\n        authEvent.emit('project_user.invite.pending', eventData);\n        return res.json({ msg: \"User not found, save invite in pending \", pendingInvitation: savedPendingInvitation });\n      }).catch(function (err) {\n        return res.send(err);\n      });\n\n    } else if (req.user.id == user._id) {\n      // If the current user id is = to the id of found user return an error. To a user is not allowed to invite oneself.\n      winston.debug('Invite User: found user ' + user._id + ' is equal to current user ' + req.user.id + '. Is not allowed to invite oneself.')\n      return res.status(403).send({ success: false, msg: 'Forbidden. It is not allowed to invite oneself', code: 4000 });\n\n    } else {\n\n      /**\n       * *** IT IS NOT ALLOWED TO INVITE A USER WHO IS ALREADY A MEMBER OF THE PROJECT *** \n       * FIND THE PROJECT USERS FOR THE PROJECT ID PASSED BY THE CLIENT IN THE BODY OF THE REQUEST\n       * IF THE ID OF THE USER FOUND FOR THE EMAIL (PASSED IN THE BODY OF THE REQUEST - see above)\n       * MATCHES ONE OF THE USER ID CONTENTS IN THE PROJECTS USER OBJECT STOP THE WORKFLOW AND RETURN AN ERROR */\n      \n      // rolequery\n      // var role = [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT];     \n      // winston.debug(\"role\", role);\n    \n      // winston.debug(\"PROJECT USER ROUTES - req projectid\", req.projectid);\n      return Project_user.findOne({ id_project: req.projectid, id_user: user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\"}, function (err, puser) {\n      // return Project_user.find({ id_project: req.projectid, role: { $in : role }, status: \"active\"}, function (err, projectuser) {\n        //puser = projectuser\n      \n        winston.debug('PRJCT-USERS FOUND (FILTERED FOR THE PROJECT ID) ', puser )\n        if (err) {\n          winston.error(\"Error inviting an already existing user: \", err);\n          return res.status(500).send({ success: false, msg: \"An error occurred on inviting user \" + email + \" on project \" + id_project })\n        }\n\n        // if (!roles.includes(req.body.role)) {\n        //   return res.status(400).send({ success: false, msg: 'Invalid role specified: ' + req.body.role });\n        // }\n        let user_available = typeof req.body.user_available === 'boolean' ? req.body.user_available : true\n\n        if (puser) {\n          if (puser.trashed !== true) {\n            winston.warn(\"Trying to invite an already project member user\")\n            return res.status(403).send({ success: false, msg: 'Forbidden. User is already a member', code: 4001 });\n          }\n\n          winston.debug('NO ERROR, SO CREATE AND SAVE A NEW PROJECT USER ')\n\n          var newProject_user = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: req.projectid,\n            id_user: user._id,\n            role: req.body.role,         \n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: user_available, \n            createdBy: req.user.id,\n            updatedBy: req.user.id\n          });\n\n          return newProject_user.save(function (err, savedProject_user) {\n            if (err) {\n              winston.error(\"Error update existing project user before inviting it \", err)\n              return res.status(500).send({ success: false, msg: \"An error occurred on inviting user \" + email + \" on project \" + id_project })\n            }\n\n            emailService.sendYouHaveBeenInvited(email, req.user.firstname, req.user.lastname, req.project.name, id_project, user.firstname, user.lastname, req.body.role)\n\n            updatedPuser.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, updatedPuserPopulated){\n              var pu = updatedPuserPopulated.toJSON();\n              pu.isBusy = ProjectUserUtil.isBusy(savedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);\n              var eventData = {req:req, updatedPuserPopulated: pu};\n              winston.debug(\"eventData\",eventData);\n              authEvent.emit('project_user.invite', eventData);\n            });\n\n            return res.status(200).send(updatedPuser);\n          })\n\n        } else {\n\n          let newProject_user = new Project_user({\n            id_project: id_project,\n            id_user: user._id,\n            role: req.body.role,\n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: user_available,\n            createdBy: req.user.id,\n            updatedBy: req.user.id\n          })\n\n          newProject_user.save((err, savedProject_user) => {\n            if (err) {\n              winston.error(\"Error saving new project user: \", err)\n              return res.status(500).send({ success: false, msg: \"An error occurred on inviting user \" + email + \" on project \" + id_project })\n            }\n\n            emailService.sendYouHaveBeenInvited(email, req.user.firstname, req.user.lastname, req.project.name, id_project, user.firstname, user.lastname, req.body.role)\n\n            savedProject_user.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, savedProject_userPopulated){\n              var pu = savedProject_userPopulated.toJSON();\n              pu.isBusy = ProjectUserUtil.isBusy(savedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);\n              var eventData = {req:req, savedProject_userPopulated: pu};\n              winston.debug(\"eventData\",eventData);\n              authEvent.emit('project_user.invite', eventData);\n            });\n\n            return res.status(200).send(savedProject_user);\n          })\n        }\n      })\n    }\n  });\n});\n\nrouter.post('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], function (req, res) {\n\n  var newProject_user = new Project_user({\n    id_project: req.projectid, //il fullname????\n    uuid_user: uuidv4(),\n    // role: RoleConstants.USER,   \n    // - Create project_user endpoint by agent (Ticketing) now is with Guest Role      \n    role: RoleConstants.GUEST,\n    roleType : RoleConstants.TYPE_USERS,   \n    user_available: false,\n    tags: req.body.tags, \n    createdBy: req.user.id,\n    updatedBy: req.user.id\n  });\n\n  return newProject_user.save(function (err, savedProject_user) {\n    if (err) {\n      winston.error('--- > ERROR ', err)\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n    return res.json(savedProject_user);\n  });\n})\n\nrouter.put('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('agent')], function (req, res) {\n\n  winston.debug(\"projectuser patch\", req.body);\n\n  var update = {};\n  \n  if (req.body.user_available!=undefined) {\n    update.user_available = req.body.user_available;\n  }\n\n  if (req.body.profileStatus!=undefined) {\n    update.profileStatus = req.body.profileStatus;\n  }\n  \n  if (req.body.max_assigned_chat!=undefined) {\n    update.max_assigned_chat = req.body.max_assigned_chat;\n  }\n\n  if (req.body.number_assigned_requests!=undefined) {\n    update.number_assigned_requests = req.body.number_assigned_requests;\n  }\n\n  if (req.body.attributes!=undefined) {\n    update.attributes = req.body.attributes;\n  }\n\n  if (req.body[\"settings.email.notification.conversation.assigned.toyou\"]!=undefined) {\n    update[\"settings.email.notification.conversation.assigned.toyou\"] = req.body[\"settings.email.notification.conversation.assigned.toyou\"];\n  }\n\n  if (req.body[\"settings.email.notification.conversation.pooled\"]!=undefined) {\n    update[\"settings.email.notification.conversation.pooled\"] = req.body[\"settings.email.notification.conversation.pooled\"];\n  }\n  if (req.body.tags!=undefined) {\n    update.tags = req.body.tags;\n  }\n\n  Project_user.findByIdAndUpdate(req.projectuser.id, update,  { new: true, upsert: true }, function (err, updatedProject_user) {\n    if (err) {\n      winston.error(\"Error gettting project_user for update\", err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n    \n    updatedProject_user.populate({ path:'id_user', select: { 'firstname': 1, 'lastname': 1 }}, function (err, updatedProject_userPopulated) {    \n      var pu = updatedProject_userPopulated.toJSON();\n      pu.isBusy = ProjectUserUtil.isBusy(updatedProject_userPopulated, req.project.settings && req.project.settings.max_agent_assigned_chat);\n      authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req});\n    });\n    \n    res.json(updatedProject_user);\n  });\n});\n\nrouter.put('/:project_userid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('admin', ['subscription'])], function (req, res) {\n\n  winston.debug(\"project_userid update\", req.body);\n\n  var update = {};\n  \n  if (req.body.role!=undefined) {\n    update.role = req.body.role;\n  }\n\n  if (req.body.user_available!=undefined) {\n    update.user_available = req.body.user_available;\n  }\n\n  if (req.body.profileStatus!=undefined) {\n    update.profileStatus = req.body.profileStatus;\n  }\n  \n  if (req.body.max_assigned_chat!=undefined) {\n    update.max_assigned_chat = req.body.max_assigned_chat;\n  }\n\n  if (req.body.number_assigned_requests!=undefined) {\n    update.number_assigned_requests = req.body.number_assigned_requests;\n  }\n  \n  if (req.body.attributes!=undefined) {\n    update.attributes = req.body.attributes;\n  }\n\n  const allowedStatuses = ['active', 'disabled'];\n  if (req.body.status !== undefined && allowedStatuses.includes(req.body.status)) {\n      update.status = req.body.status;\n  }\n\n  if (req.body[\"settings.email.notification.conversation.assigned.toyou\"]!=undefined) {\n    update[\"settings.email.notification.conversation.assigned.toyou\"] = req.body[\"settings.email.notification.conversation.assigned.toyou\"];\n  }\n\n  if (req.body[\"settings.email.notification.conversation.pooled\"]!=undefined) {\n    update[\"settings.email.notification.conversation.pooled\"] = req.body[\"settings.email.notification.conversation.pooled\"];\n  }\n  \n  if (req.body.tags!=undefined) {\n    update.tags = req.body.tags;\n  }\n\n  winston.debug(\"project_userid update\", update);\n\n  Project_user.findByIdAndUpdate(req.params.project_userid, update, { new: true, upsert: true }, function (err, updatedProject_user) {\n    if (err) {\n      winston.error(\"Error gettting project_user for update\", err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n      updatedProject_user.populate({path:'id_user', select:{'firstname':1, 'lastname':1}},function (err, updatedProject_userPopulated){    \n        if (err) {\n          winston.error(\"Error gettting updatedProject_userPopulated for update\", err);\n        }            \n        var pu = updatedProject_userPopulated.toJSON();\n        pu.isBusy = ProjectUserUtil.isBusy(updatedProject_user, req.project.settings && req.project.settings.max_agent_assigned_chat);\n        \n          authEvent.emit('project_user.update', {updatedProject_userPopulated:pu, req: req});\n      });\n    \n\n    res.json(updatedProject_user);\n  });\n});\n\n// TODO fai servizio di patch degli attributi come request\n// TODO  blocca cancellazione owner?\nrouter.delete('/:project_userid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n\n  const { hard, soft } = req.query;\n  const pu_id = req.params.project_userid;\n\n  winston.debug(req.body);\n\n  if (soft === \"true\") {\n    // Soft Delete\n    Project_user.findByIdAndUpdate(pu_id, { trashed: true }, { new: true }, (err, project_user) => {\n      if (err) {\n        winston.error(\"Error gettting project_user for soft delete\", err);\n        return res.status(500).send({ success: false, msg: 'Error deleting Project User with id ' + pu_id });\n      }\n\n      winston.debug(\"Soft deleted project_user\", project_user);\n      if (!project_user) {\n        winston.warn(\"Project user not found for soft delete with id \" + pu_id);\n        return res.status(404).send({ success: false, error: 'Project user not found with id ' + pu_id });\n      }\n\n      puEvent.emit('project_user.deleted', project_user);\n      // Event 'project_user.delete' not working - Check it and improve it to manage soft/hard delete\n      return res.status(200).send(project_user);\n\n    })\n  }\n  else if (hard === \"true\") {\n    // Hard Delete\n    Project_user.findByIdAndRemove(pu_id, { new: false }, (err, project_user) => {\n      if (err) {\n        winston.error(\"Error gettting project_user for hard delete\", err);\n        return res.status(500).send({ success: false, msg: 'Error deleting Project user with id ' + pu_id });\n      }\n\n      if (!project_user) {\n        winston.warn(\"Project user not found for soft delete with id \" + pu_id);\n        return res.status(404).send({ success: false, error: 'Project user not found with id ' + pu_id });\n      }\n\n      winston.debug(\"Hard deleted project_user\", project_user);\n\n      if (project_user) {\n        project_user.populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1 } }, function (err, project_userPopulated) {\n          authEvent.emit('project_user.delete', { req: req, project_userPopulated: project_userPopulated });\n        });\n      }\n\n      puEvent.emit('project_user.deleted', project_user);\n      return res.status(200).send(project_user);\n    });\n  }\n  else {\n    // Disable\n    Project_user.findByIdAndUpdate(pu_id, { status: \"disabled\", user_available: false }, { new: true }, (err, project_user) => {\n        if (err) {\n          winston.error(\"Error gettting project_user for disable user\", err);\n          return res.status(500).send({ success: false, msg: 'Error disabling Project User with id ' + pu_id });\n        }\n\n        if (!project_user) {\n          winston.warn(\"Project user not found for soft delete with id \" + pu_id);\n          return res.status(404).send({ success: false, error: 'Project user not found with id ' + pu_id });\n        }\n\n        winston.debug(\"Disabled project_user\", project_user);\n\n        // Event 'project_user.delete' not working - Check it and improve it to manage disable project user\n        return res.status(200).send(project_user);\n      });\n  }\n});\n\n// Restore a soft-deleted (trashed) project user. Fails if not found or not trashed.\nrouter.put('/:project_userid/restore', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRole('admin')], function (req, res) {\n  const pu_id = req.params.project_userid;\n\n  Project_user.findOne({ _id: pu_id, id_project: req.projectid }, function (err, project_user) {\n    if (err) {\n      winston.error(\"Error finding project_user for restore\", err);\n      return res.status(500).send({ success: false, msg: 'Error restoring Project User with id ' + pu_id });\n    }\n    if (!project_user) {\n      winston.warn(\"Project user not found for restore with id \" + pu_id);\n      return res.status(404).send({ success: false, error: 'Project user not found with id ' + pu_id });\n    }\n    if (project_user.trashed !== true) {\n      winston.warn(\"Project user is not trashed, cannot restore id \" + pu_id);\n      return res.status(400).send({ success: false, error: 'Project user is not trashed, cannot restore' });\n    }\n\n    Project_user.findByIdAndUpdate(pu_id, { trashed: false, status: 'active' }, { new: true }, function (err, updatedProject_user) {\n      if (err) {\n        winston.error(\"Error restoring project_user\", err);\n        return res.status(500).send({ success: false, msg: 'Error restoring Project User with id ' + pu_id });\n      }\n\n      winston.debug(\"Restored project_user\", updatedProject_user);\n      updatedProject_user.populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1 } }, function (err, updatedProject_userPopulated) {\n        if (err) {\n          winston.error(\"Error populating restored project_user\", err);\n        } else {\n          var pu = updatedProject_userPopulated.toJSON();\n          pu.isBusy = ProjectUserUtil.isBusy(updatedProject_user, req.project && req.project.settings && req.project.settings.max_agent_assigned_chat);\n          authEvent.emit('project_user.update', { updatedProject_userPopulated: pu, req: req });\n        }\n      });\n      return res.status(200).send(updatedProject_user);\n    });\n  });\n});\n\nrouter.get('/me', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], function (req, res, next) {\n  if (!req.project) {\n    return res.status(404).send({ success: false, msg: 'Project not found.' });\n  }\n  var project_user = req.projectuser;\n\n  var pu = project_user.toJSON();\n   \n  pu.isBusy = ProjectUserUtil.isBusy(project_user, req.project.settings && req.project.settings.max_agent_assigned_chat);\n  res.json([pu]);\n\n\n});\n\nrouter.get('/:project_userid', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], function (req, res) {\n  // router.get('/details/:project_userid', function (req, res) {\n  // winston.debug(\"PROJECT USER ROUTES - req projectid\", req.projectid);\n  Project_user.findOne({ _id: req.params.project_userid, id_project: req.projectid}).\n    populate('id_user'). //qui cache importante ma populatevirtual\n    exec(function (err, project_user) {\n      if (err) {\n        winston.error(\"Error gettting project_user for get\", err);\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      if (!project_user) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n      // res.json(project_user);\n      var pu = project_user.toJSON();\n      pu.isBusy = ProjectUserUtil.isBusy(project_user, req.project.settings && req.project.settings.max_agent_assigned_chat);\n      res.json(pu);\n    });\n\n});\n\nrouter.get('/users/search', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('user', ['subscription'])], async (req, res, next) => { //changed for smtp \n  // router.get('/users/search', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], async (req, res, next) => {\n  winston.debug(\"--> users search  \");\n\n  if (!req.project) {\n    return res.status(404).send({ success: false, msg: 'Project not found.' });\n  }\n\n\n  let query =  {email: req.query.email};\n  \n  winston.debug('query: ', query);\n  \n  let user = await User.findOne(query).exec();\n  winston.debug('user: ', user);\n  \n  if (!user) {\n    return res.status(404).send({ success: false, msg: 'Object not found.' });\n  }\n \n\n  let project_user = await Project_user.findOne({id_user: user._id, id_project: req.projectid}).exec();\n  winston.debug('project_user: ', project_user);\n  \n  if (!project_user) {\n    return res.status(403).json({msg: \"Unauthorized. This is not a your teammate.\" });\n  }\n  \n\n  return res.json({_id: user._id});\n\n});\n\n/**\n * GET PROJECT-USER BY PROJECT ID AND CURRENT USER ID \n//  */\nrouter.get('/users/:user_id', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], function (req, res, next) {\n  winston.debug(\"--> users USER ID \", req.params.user_id);\n\n  if (!req.project) {\n    return res.status(404).send({ success: false, msg: 'Project not found.' });\n  }\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(req.params.user_id);\n  winston.debug(\"isObjectId:\"+ isObjectId);\n\n  var queryProjectUser ={ id_project: req.projectid};\n\n  \n  if (isObjectId) {          \n    queryProjectUser.id_user = req.params.user_id\n  }else {\n    queryProjectUser.uuid_user = req.params.user_id\n  }\n\n  var q1 = Project_user.findOne(queryProjectUser);\n\n  if (isObjectId) {    \n    q1.populate('id_user'); //qui cache importante ma populatevirtual\n  }\n\n //  Project_user.findOne({ id_user: req.params.user_id, id_project: req.projectid }).\n  \n   q1.exec(function (err, project_user) {\n     if (err) {\n       winston.error(\"Error gettting project_user for get users\", err);\n       return res.status(500).send({ success: false, msg: 'Error getting object.' });\n     }\n     if (!project_user) {\n       return res.status(404).send({ success: false, msg: 'Object not found.' });\n     }\n    \n     // res.json(project_user);\n     var pu = project_user.toJSON();\n\n\n   \n     pu.isBusy = ProjectUserUtil.isBusy(project_user, req.project.settings && req.project.settings.max_agent_assigned_chat);\n     res.json([pu]);\n\n    });\n});\n\n\n  // TODO if project is deleted\n\n// 2020-03-31T17:25:45.939421+00:00 app[web.1]: \n// 2020-03-31T17:25:45.998260+00:00 app[web.1]: error: uncaughtException: Cannot read property 'settings' of undefined\n// 2020-03-31T17:25:45.998262+00:00 app[web.1]: TypeError: Cannot read property 'settings' of undefined\n// 2020-03-31T17:25:45.998262+00:00 app[web.1]:     at /app/routes/project_user.js:372:68\n// 2020-03-31T17:25:45.998263+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:4779:16\n// 2020-03-31T17:25:45.998263+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/utils.js:276:16\n// 2020-03-31T17:25:45.998263+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:4798:21\n// 2020-03-31T17:25:45.998264+00:00 app[web.1]:     at _hooks.execPost (/app/node_modules/mongoose/lib/query.js:4364:11)\n// 2020-03-31T17:25:45.998264+00:00 app[web.1]:     at /app/node_modules/kareem/index.js:135:16\n// 2020-03-31T17:25:45.998269+00:00 app[web.1]:     at processTicksAndRejections (internal/process/next_tick.js:74:9) {\"error\":{},\"stack\":\"TypeError: Cannot read property 'settings' of undefined\\n    at /app/routes/project_user.js:372\n// \n\n/**\n * RETURN THE PROJECT-USERS OBJECTS FILTERD BY PROJECT-ID AND WITH NESTED THE USER OBJECT\n * WF: 1. GET PROJECT-USER by the passed project ID\n *     2. POPULATE THE user_id OF THE PROJECT-USER object WITH THE USER OBJECT\n */                                                                                       \nrouter.get('/', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['bot', 'subscription'])], function (req, res) {\n\n  // rolequery\n  // var role = [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT];\n\n  var query;\n  if (req.query.role) {\n    role = req.query.role;\n    winston.debug(\"role\", role);\n    query =  {id_project: req.projectid, role: { $in : role } };\n  } else {\n     query =  {id_project: req.projectid, roleType: RoleConstants.TYPE_AGENTS };\n  }\n\n  if (!req.query.trashed || req.query.trashed === 'false' || req.query.trashed === false) {\n    query.trashed = { $ne: true };\n  }\n\n  // var query =  {id_project: req.projectid, role: { $in : role } };\n  \n\n  if (req.query.presencestatus) {\n    query[\"presence.status\"] = req.query.presencestatus;\n  }\n\n  winston.debug(\"query\", query);\n\n  if (req.query.status) {\n    query[\"status\"] = req.query.status;\n  }\n\n  Project_user.find(query).\n    populate('id_user').\n    // lean().                   \n    exec(function (err, project_users) {\n      if (err) {\n        winston.error(\"Error gettting project_user for get users\", err);\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n\n      var ret = [];\n\n      project_users.forEach(function(project_user) {\n        var pu = project_user.toJSON();\n        pu.isBusy = ProjectUserUtil.isBusy(project_user, req.project && req.project.settings && req.project.settings.max_agent_assigned_chat);\n        ret.push(pu);\n      });\n\n      res.json(ret);\n      \n    });\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/project_user_test.js",
    "content": "var express = require('express');\nvar router = express.Router({mergeParams: true});\n\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\nvar validtoken = require('../middleware/valid-token')\nvar roleChecker = require('../middleware/has-role');\n\n\n\nrouter.get('/test', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes('agent', ['subscription'])], function (req, res) {\n      // console.log(\"req.projectuser\",req.projectuser);      \n      res.json(req.projectuser);\n\n});\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/property.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Property = require(\"../models/property\");\nvar winston = require('../config/winston');\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var newProperty= new Property({\n    label: req.body.label,\n    name: req.body.name,\n    type: req.body.type,\n    id_project: req.projectid,\n    createdBy: req.user._id,\n  });\n\n  newProperty.save(function(err, savedProperty) {\n    if (err) {\n      winston.error('Error saving the property '+ JSON.stringify(savedProperty), err)\n      return reject(err);\n    }            \n    winston.verbose('Property created ', savedProperty.toJSON());\n\n    res.json(savedProperty);\n  });\n\n});\n\nrouter.put('/:propertyid', function (req, res) {\n  winston.debug(req.body);\n  var update = {};\n  \n  if (req.body.label!=undefined) {\n    update.label = req.body.label;\n  }\n  \n  if (req.body.name!=undefined) {\n    update.name = req.body.name;\n  }\n  if (req.body.type!=undefined) {\n    update.type = req.body.type;\n  }\n  if (req.body.status!=undefined) {\n    update.status = req.body.status;\n  }\n\n  \n  Property.findByIdAndUpdate(req.params.propertyid, update, { new: true, upsert: true }, function (err, updatedProperty) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }   \n  \n    res.json(updatedProperty);\n  });\n});\n\n\n\n\nrouter.delete('/:propertyid', function (req, res) {\n  winston.debug(req.body);\n\n  Property.findByIdAndUpdate(req.params.propertyid, {status: 1000}, { new: true, upsert: true }, function (err, updatedProperty) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    res.json(updatedProperty);\n  });\n});\n\nrouter.delete('/:propertyid/physical', function (req, res) {\n  winston.debug(req.body);\n\n  var projectuser = req.projectuser;\n\n\n  if (projectuser.role != \"owner\" ) {\n    return res.status(403).send({ success: false, msg: 'Unauthorized.' });\n  }\n  \n   Property.remove({ _id: req.params.propertyid }, function (err, property) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n\n    res.json(property);\n  });\n});\n\n\n\nrouter.get('/:propertyid', function (req, res) {\n  winston.debug(req.body);\n\n  Property.findById(req.params.propertyid, function (err, property) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!property) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(property);\n  });\n});\n\n\nrouter.get('/', function (req, res) {\n\n  var limit = 40; // Number of request per page\n\n  if (req.query.limit) {    \n    limit = parseInt(req.query.limit);\n    winston.debug('property ROUTE - limit: '+limit);\n  }\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('property ROUTE - SKIP PAGE ', skip);\n\n\n  var query = { \"id_project\": req.projectid, \"status\": 100};\n\n  if (req.query.name) {\n    winston.debug('property ROUTE req.query.name', req.query.name);\n    query.name = req.query.name;\n  }\n\n  if (req.query.status) {\n    query.status = req.query.status;\n  }\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);  \n\n  return Property.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, properties) {\n      if (err) {\n        winston.error('property ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n     \n        return res.json(properties);\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/public-analytics.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar AnalyticResult = require(\"../models/analyticResult\");\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n// var cacheUtil = require('../utils/cacheUtil');\n\n\n  router.get('/waiting/current', function(req, res) {\n  \n    //winston.debug(req.params);\n    //winston.debug(\"req.projectid\",  req.projectid);    \n   \n    // res.json([]);\n\n\n    AnalyticResult.aggregate([\n        //last 4\n        { $match: {\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (4 * 60 * 60 * 1000))) }} },\n        { \"$group\": { \n          \"_id\": \"$id_project\", \n         \"waiting_time_avg\":{\"$avg\": \"$waiting_time\"}\n        }\n      },\n      \n    ])\n      // .cache(cacheUtil.longTTL, req.projectid+\":analytics:query:waiting:avg:4hours\")        \n      .exec(function(err, result) {\n\n          if (err) {\n            winston.debug(err);\n            return res.status(500).send({success: false, msg: 'Error getting analytics.'});\n          }\n          //winston.debug(result);\n\n          res.json(result);\n    });\n\n  });\n\n\n\n  \n\n\n  \n\n  \n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/public-request.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Message = require(\"../models/message\");\nvar Request = require(\"../models/request\");\nvar User = require(\"../models/user\");\nvar winston = require('../config/winston');\n\nvar fonts = {\n\tRoboto: {\n\t\tnormal: 'fonts/Roboto-Regular.ttf',\n\t\tbold: 'fonts/Roboto-Medium.ttf',\n\t\titalics: 'fonts/Roboto-Italic.ttf',\n\t\tbolditalics: 'fonts/Roboto-MediumItalic.ttf'\n\t}\n};\n\nvar PdfPrinter = require('pdfmake');\nvar printer = new PdfPrinter(fonts);\n// var fs = require('fs');\n\n\n\n\n\n\n  router.get('/:requestid/messages', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n      return res.json(messages);\n    });\n\n  });\n\n\n  router.get('/:requestid/messages.html', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n      return res.render('messages', \n        { title: 'Tiledesk', \n          messages: messages,\n          brandName: process.env.BRAND_NAME || null,\n          brandLogo: process.env.BRAND_LOGO || null\n        });\n    });\n\n  });\n\n\n  router.get('/:requestid/messages.csv', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).lean().exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n      messages.forEach(function(element) {\n\n        var channel_name = \"\";\n        if (element.channel && element.channel.name) {\n          channel_name = element.channel.name;\n        }\n        delete element.channel;\n        element.channel_name = channel_name;\n\n        delete element.attributes;\n      });\n\n      res.setHeader('Content-Type', 'applictext/csv');\n      res.setHeader('Content-Disposition', 'attachment; filename=transcript.csv');\n\n      return res.csv(messages, true);\n    });\n\n  });\n\n\n  router.get('/:requestid/messages.txt', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n      \n\n      var text = \"Chat transcript:\\n\" //+ req.project.name;\n      \n      messages.forEach(function(element) {\n        text = text + \"[ \" + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ \"] \" + element.senderFullname + \": \" + element.text + \"\\n\";\n      });\n    \n\n      res.set({\"Content-Disposition\":\"attachment; filename=\\\"transcript.txt\\\"\"});\n      res.send(text);\n    });\n\n  });\n\n\n\n  router.get('/:requestid/messages.pdf', function(req, res) {\n\n\n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n\n      var docDefinition = {\n        content: [\n          { text: 'Chat Transcript', style: 'header' },\n          {\n            ul: [\n              // 'item 1',\n              // 'item 2',\n              // 'item 3'\n            ]\n          },\n          \n        ],\n        styles: {\n          header: {\n            bold: true,\n            fontSize: 15\n          }\n        },\n        defaultStyle: {\n          fontSize: 12\n        }\n      };\n\n      \n\n      messages.forEach(function(element) {\n        docDefinition.content[1].ul.push(\"[ \" + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ \"] \" + element.senderFullname + \": \" + element.text );\n      });\n\n      console.log(docDefinition);\n   \n    var pdfDoc = printer.createPdfKitDocument(docDefinition);\n    // pdfDoc.pipe(fs.createWriteStream('lists.pdf'));\n\n    res.setHeader('Content-Type', 'application/pdf');\n    res.setHeader('Content-Disposition', 'attachment; filename=transcript.pdf');\n            \n\n    pdfDoc.pipe(res);\n    pdfDoc.end();\n\n\n      \n    });\n\n  });\n\n\n\n  router.get('/:requestid/messages-user.html', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      var messages = messages.filter(m => m.sender != \"system\" );\n\n\n      //skip info message\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n      return res.render('messages', { title: 'Tiledesk', messages: messages});\n    });\n\n  });\n\n\n\n  router.get('/:requestid/messages-user.txt', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n      \n\n      var messages = messages.filter(m => m.sender != \"system\" );\n\n      var text = \"Chat transcript:\\n\" //+ req.project.name;\n\n      messages.forEach(function(element) {\n        text = text + \"[ \" + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ \"] \" + element.senderFullname + \": \" + element.text + \"\\n\";\n      });\n    \n\n      res.set({\"Content-Disposition\":\"attachment; filename=\\\"transcript.txt\\\"\"});\n      res.send(text);\n    });\n\n  });\n\n\n  router.get('/:requestid/messages-user.pdf', function(req, res) {\n\n\n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      var messages = messages.filter(m => m.sender != \"system\" );\n\n\n      //skip info message\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n\n      var docDefinition = {\n        content: [\n          { text: 'Chat Transcript', style: 'header' },\n          {\n            ul: [\n              // 'item 1',\n              // 'item 2',\n              // 'item 3'\n            ]\n          },\n          \n        ],\n        styles: {\n          header: {\n            bold: true,\n            fontSize: 15\n          }\n        },\n        defaultStyle: {\n          fontSize: 12\n        }\n      };\n\n      \n\n      messages.forEach(function(element) {\n        docDefinition.content[1].ul.push(\"[ \" + element.createdAt.toLocaleString('en', { timeZone: 'UTC' })+ \"] \" + element.senderFullname + \": \" + element.text );\n      });\n\n      console.log(docDefinition);\n   \n    var pdfDoc = printer.createPdfKitDocument(docDefinition);\n    // pdfDoc.pipe(fs.createWriteStream('lists.pdf'));\n\n    res.setHeader('Content-Type', 'application/pdf');\n    res.setHeader('Content-Disposition', 'attachment; filename=transcript.pdf');\n    \n    pdfDoc.pipe(res);\n    pdfDoc.end();\n\n\n      \n    });\n\n  });\n\n\n  router.get('/:requestid/messages-user.csv', function(req, res) {\n  \n    winston.debug(req.params);\n    winston.debug(\"here\");    \n    return Message.find({\"recipient\": req.params.requestid}).sort({createdAt: 'asc'}).lean().exec(function(err, messages) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error getting object.'});\n      }\n\n      var messages = messages.filter(m => m.sender != \"system\" );\n\n\n      //skip info message\n      if(!messages){\n        return res.status(404).send({success: false, msg: 'Object not found.'});\n      }\n\n\n      messages.forEach(function(element) {\n\n        var channel_name = \"\";\n        if (element.channel && element.channel.name) {\n          channel_name = element.channel.name;\n        }\n        delete element.channel;\n        element.channel_name = channel_name;\n\n        delete element.attributes;\n      });\n\n\n      res.setHeader('Content-Type', 'applictext/csv');\n      res.setHeader('Content-Disposition', 'attachment; filename=transcript.csv');\n\n      return res.csv(messages, true);\n    });\n\n  });\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/quotes.js",
    "content": "var express = require('express');\nvar router = express.Router();\nconst { QuoteManager } = require('../services/QuoteManager');\nlet winston = require('../config/winston');\nconst { MODELS_MULTIPLIER } = require('../utils/aiUtils');\n\n\nrouter.post('/', async (req, res) => {\n\n    let date = req.body.date;\n\n    let obj = { createdAt: new Date() };\n    if (date) {\n        obj.createdAt = new Date(date)\n    }\n\n    let quoteManager = req.app.get('quote_manager');\n\n    // check if project is not null/undefined\n    let quotes = await quoteManager.getAllQuotes(req.project, obj);\n    let currentSlot = await quoteManager.getCurrentSlot(req.project);\n\n    winston.debug(\"quotes: \", quotes);\n    res.status(200).send({ success: true, quotes: quotes, slot: currentSlot });\n})\n\nrouter.get('/:type', async (req, res) => {\n\n    let type = req.params.type;\n    let obj = { createdAt: new Date() };\n\n    let quoteManager = req.app.get('quote_manager');\n    let isAvailable = await quoteManager.checkQuote(req.project, obj, type);\n\n    winston.debug(\"is \" + type + \" available: \", isAvailable);\n    res.status(200).send({ isAvailable: isAvailable })\n\n})\n\nrouter.post('/incr/:type', async (req, res) => {\n\n    let type = req.params.type;\n    let data = req.body;\n\n    let quoteManager = req.app.get('quote_manager');\n\n    let modelKey;\n    if (typeof data.model === 'string') {\n        modelKey = data.model;\n    } else if (data.model && typeof data.model.name === 'string') {\n        modelKey = data.model.name;\n    }\n\n    let multiplier = MODELS_MULTIPLIER[modelKey];\n    if (!multiplier) {\n        multiplier = 1;\n        winston.info(\"No multiplier found for AI model (incr) \" + modelKey)\n    }\n    data.multiplier = multiplier;\n    data.createdAt = new Date();\n\n    let incremented_key = await quoteManager.incrementTokenCount(req.project, data);\n    let quote = await quoteManager.getCurrentQuote(req.project, data, type);    \n\n    res.status(200).send({ message: \"value incremented for key \" + incremented_key, key: incremented_key, currentQuote: quote });\n})\n\nmodule.exports = router;"
  },
  {
    "path": "routes/request.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Request = require(\"../models/request\");\nvar mongoose = require('mongoose');\nvar Schema = mongoose.Schema,\n  ObjectId = Schema.ObjectId;\nvar moment = require('moment');\nvar requestService = require('../services/requestService');\nvar emailService = require('../services/emailService');\n\nvar departmentService = require('../services/departmentService');\nvar winston = require('../config/winston');\nconst requestEvent = require('../event/requestEvent');\nvar Subscription = require(\"../models/subscription\");\nvar leadService = require('../services/leadService');\nvar messageService = require('../services/messageService');\nconst uuidv4 = require('uuid/v4');\nvar MessageConstants = require(\"../models/messageConstants\");\nvar Message = require(\"../models/message\");\nvar cacheUtil = require('../utils/cacheUtil');\nvar RequestConstants = require(\"../models/requestConstants\");\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar Project_user = require(\"../models/project_user\");\nvar Lead = require(\"../models/lead\");\nvar UIDGenerator = require(\"../utils/UIDGenerator\");\nlet { Publisher } = require(\"@tiledesk/tiledesk-multi-worker\");\n\ncsv = require('csv-express');\ncsv.separator = ';';\n\nconst { check, validationResult } = require('express-validator');\nconst RoleConstants = require('../models/roleConstants');\nconst eventService = require('../pubmodules/events/eventService');\nconst { Scheduler } = require('../services/Scheduler');\nconst faq_kb = require('../models/faq_kb');\n\nconst datesUtil = require('../utils/datesUtil');\n//const JobManager = require('../utils/jobs-worker-queue-manager-v2/JobManagerV2');\n\n// var messageService = require('../services/messageService');\n\nconst AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;\n\nlet jobManager = new Publisher(AMQP_MANAGER_URL, {\n  debug: false,\n  queueName: \"conversation-tags_queue\",\n  exchange: \"tiledesk-multi\",\n  topic: \"conversation-tags\",\n})\n\njobManager.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"KbRoute - ConnectPublisher done with status: \", status);\n  }\n})\n\n\n\nrouter.post('/simple', [check('first_text').notEmpty()], async (req, res) => {\n\n  var startTimestamp = new Date();\n  const errors = validationResult(req);\n\n  if (!errors.isEmpty()) {\n    return res.status(422).json({ errors: errors.array() });\n  }\n\n  if (req.projectuser) {\n    winston.debug(\"req.projectuser\", req.projectuser);\n  }\n\n  var project_user = req.projectuser;\n})\n\n// undocumented, used by test\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.create\nrouter.post('/',\n  [\n    check('first_text').notEmpty(),\n  ],\n  async (req, res) => {\n\n    var startTimestamp = new Date();\n    winston.debug(\"request create timestamp: \" + startTimestamp);\n\n    winston.debug(\"req.body\", req.body);\n\n    winston.debug(\"req.projectid: \" + req.projectid);\n    winston.debug(\"req.user.id: \" + req.user.id);\n\n\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) {\n      return res.status(422).json({ errors: errors.array() });\n    }\n\n    if (req.projectuser) {\n      winston.debug(\"req.projectuser\", req.projectuser);\n    }\n\n    var project_user = req.projectuser;\n\n    var sender = req.body.sender;\n    var fullname = req.body.senderFullname || req.user.fullName;\n    var email = req.body.email || req.user.email;\n\n    let messageStatus = req.body.status || MessageConstants.CHAT_MESSAGE_STATUS.SENDING;\n    winston.debug('messageStatus: ' + messageStatus);\n\n    var request_id = req.body.request_id || 'support-group-' + req.projectid + \"-\" + UIDGenerator.generate();\n    winston.debug('request_id: ' + request_id);\n\n    if (sender) {\n\n      var isObjectId = mongoose.Types.ObjectId.isValid(sender);\n      winston.debug(\"isObjectId:\" + isObjectId);\n\n      var queryProjectUser = { id_project: req.projectid, status: \"active\" };\n\n      if (isObjectId) {\n        queryProjectUser.id_user = sender;\n      } else {\n        queryProjectUser.uuid_user = sender;\n      }\n\n      winston.debug(\"queryProjectUser\", queryProjectUser);\n\n      project_user = await Project_user.findOne(queryProjectUser).populate({ path: 'id_user', select: { 'firstname': 1, 'lastname': 1, 'email': 1 } })\n      winston.debug(\"project_user\", project_user);\n\n      if (!project_user) {\n        return res.status(403).send({ success: false, msg: 'Unauthorized. Project_user not found with user id  : ' + sender });\n      }\n\n      if (project_user.id_user) {\n        fullname = project_user.id_user.fullName;\n        winston.debug(\"pu fullname: \" + fullname);\n        email = project_user.id_user.email;\n        winston.debug(\"pu email: \" + email);\n      } else if (project_user.uuid_user) {\n        var lead = await Lead.findOne({ lead_id: project_user.uuid_user, id_project: req.projectid });\n        winston.debug(\"lead: \", lead);\n        if (lead) {\n          fullname = lead.fullname;\n          winston.debug(\"lead fullname: \" + fullname);\n          email = lead.email;\n          winston.debug(\"lead email: \" + email);\n        } else {\n          winston.warn(\"lead not found: \" + JSON.stringify({ lead_id: project_user.uuid_user, id_project: req.projectid }));\n        }\n\n      } else {\n        winston.warn(\"pu fullname and email empty\");\n      }\n\n    }\n\n\n    // createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes) {\n    return leadService.createIfNotExistsWithLeadId(sender || req.user._id, fullname, email, req.projectid, null, req.body.attributes || req.user.attributes)\n      .then(function (createdLead) {\n\n\n\n        var new_request = {\n          request_id: request_id,\n          project_user_id: req.projectuser._id,\n          lead_id: createdLead._id,\n          id_project: req.projectid,\n          first_text: req.body.first_text,\n          departmentid: req.body.departmentid,\n          sourcePage: req.body.sourcePage,\n          language: req.body.language,\n          userAgent: req.body.userAgent,\n          status: null,\n          createdBy: req.user._id,\n          attributes: req.body.attributes,\n          subject: req.body.subject,\n          preflight: undefined,\n          channel: req.body.channel,\n          location: req.body.location,\n          participants: req.body.participants,\n          lead: createdLead, requester: project_user,\n          priority: req.body.priority,\n          followers: req.body.followers,\n        };\n\n        return requestService.create(new_request).then(function (savedRequest) {\n        \n\n          winston.debug('res.json(savedRequest)');\n          var endTimestamp = new Date();\n          winston.verbose(\"request create end: \" + (endTimestamp - startTimestamp));\n          return res.json(savedRequest);\n          // });\n          // });\n        }).catch((err) => {\n          winston.error(\"(Request) create request error \", err)\n          return res.status(500).send({ success: false, message: \"Unable to create request\", err: err })\n        });\n\n\n\n\n\n      }).catch(function (err) {\n        winston.error('Error saving request.', err);\n        return res.status(500).send({ success: false, msg: 'Error saving object.', err: err });\n\n      });\n  });\n\n\n\n\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.patch('/:requestid', function (req, res) {\n  winston.debug(req.body);\n  // const update = _.assign({ \"updatedAt\": new Date() }, req.body);\n  //const update = req.body;\n  const update = {};\n\n  if (req.body.lead) {\n    update.lead = req.body.lead;\n  }\n\n  // TODO test it. does it work?\n  if (req.body.status) {\n    update.status = req.body.status;\n  }\n\n  if (req.body.tags) {\n    update.tags = req.body.tags;\n  }\n\n  // if (req.body.notes) {\n  //   update.notes = req.body.notes;\n  // }\n\n  if (req.body.rating) {\n    update.rating = req.body.rating;\n  }\n\n  if (req.body.rating_message) {\n    update.rating_message = req.body.rating_message;\n  }\n\n  if (req.body.sourcePage) {\n    update.sourcePage = req.body.sourcePage;\n  }\n\n  if (req.body.language) {\n    update.language = req.body.language;\n  }\n\n  if (req.body.first_text) {\n    update.first_text = req.body.first_text;\n  }\n\n  if (req.body.subject) {\n    update.subject = req.body.subject;\n  }\n\n  if (req.body.location) {\n    update.location = req.body.location;\n  }\n  if (req.body.priority) {\n    update.priority = req.body.priority;\n  }\n\n  if (req.body.smartAssignment != undefined) {\n    update.smartAssignment = req.body.smartAssignment;\n  }\n\n  if (req.body.workingStatus != undefined) {\n    update.workingStatus = req.body.workingStatus;\n  }\n\n\n  if (req.body.channelName) {\n    update[\"channel.name\"] = req.body.channelName;\n  }\n\n\n\n  winston.verbose(\"Request patch update\", update);\n\n  //cacheinvalidation\n  return Request.findOneAndUpdate({ \"request_id\": req.params.requestid, \"id_project\": req.projectid }, { $set: update }, { new: true, upsert: false })\n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')\n    .populate({ path: 'requester', populate: { path: 'id_user' } })\n    .exec(function (err, request) {\n\n      if (err) {\n        winston.error('Error patching request.', err);\n        return res.status(500).send({ success: false, msg: 'Error updating object.' });\n      }\n\n      if (!request) {\n        return res.status(404).send({ success: false, msg: 'Request not found' });\n      }\n\n      if (update.workingStatus !== undefined) {\n        requestEvent.emit('request.workingStatus.update', { request });\n      }\n\n      requestEvent.emit(\"request.update\", request);\n      requestEvent.emit(\"request.update.comment\", { comment: \"PATCH\", request: request }); //Deprecated\n      requestEvent.emit(\"request.updated\", { comment: \"PATCH\", request: request, patch: update });\n      return res.json(request);\n    });\n\n});\n\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.put('/:requestid/close', async function (req, res) {\n  winston.debug(req.body);\n  let request_id = req.params.requestid;\n  \n  /**\n   * Check on projectuser existence.\n   * If req.projectuser is null then the request was made by a chatbot.\n   */\n  let project_user = req.projectuser;\n  let user_role;\n  if (project_user) {\n    user_role = project_user.role;\n  }\n\n  const closed_by = req.user.id;\n\n  if (user_role && (user_role !== RoleConstants.OWNER && user_role !== RoleConstants.ADMIN)) {\n    let request = await Request.findOne({ id_project: req.projectid, request_id: request_id }).catch((err) => {\n      winston.error(\"Error finding request: \", err);\n      return res.status(500).send({ success: false, error: \"Error finding request with request_id \" + request_id })\n    })\n  \n    if (!request) {\n      winston.verbose(\"Request with request_id \" + request_id)\n      return res.status(404).send({ success: false, error: \"Request not found\"})\n    }\n  \n    if (!request.participantsAgents.includes(req.user.id)) {\n      winston.verbose(\"Request can't be closed by a non participant. Attempt made by \" + req.user.id);\n      return res.status(403).send({ success: false, error: \"You must be among the participants to close a conversation.\"})\n    }\n  }\n\n  return requestService.closeRequestByRequestId(req.params.requestid, req.projectid, false, true, closed_by, req.body.force).then(function (closedRequest) {\n    winston.verbose(\"request closed\", closedRequest);\n    return res.json(closedRequest);\n  }).catch(function(err) {\n    winston.error(\"Error closing request\", err);\n    return res.status(500).send({ success: false, error: \"Error closing request\" });\n  });\n\n});\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.put('/:requestid/reopen', function (req, res) {\n  winston.debug(req.body); \n  // reopenRequestByRequestId(request_id, id_project) {\n  return requestService.reopenRequestByRequestId(req.params.requestid, req.projectid).then(function (reopenRequest) {\n\n    winston.verbose(\"request reopen\", reopenRequest);\n\n    return res.json(reopenRequest);\n  }).catch(function(err) {\n    winston.error(\"Error reopening request\", err);\n    return res.status(500).send({ success: false, error: \"Error reopening request\" });\n  });\n\n\n});\n\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.put('/:requestid/assignee', function (req, res) {\n  winston.debug(req.body);\n  //TODO change assignee\n});\n\n// curl -v -X POST -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"member\":\"ciao\"}' http://localhost:3000/68d4135a10e71f7bfa4dcf2f/requests/req123456/participants\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.post('/:requestid/participants',\n  [\n    check('member').notEmpty(),\n  ],\n   async (req, res) => {\n    winston.debug(req.body);\n\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) {\n      return res.status(422).json({ errors: errors.array() });\n    }\n\n    //addParticipantByRequestId(request_id, id_project, member)\n    return requestService.addParticipantByRequestId(req.params.requestid, req.projectid, req.body.member).then(function (updatedRequest) {\n\n      winston.verbose(\"participant added\", updatedRequest);\n\n      return res.json(updatedRequest);\n    }).catch(function(err) {\n      winston.error(\"Error adding participant\", err);\n      return res.status(500).send({ success: false, error: \"Error adding participant\" });\n    });\n\n  });\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\n/*\nerror: uncaughtException: Cannot set property 'participants' of null\n2020-03-08T12:53:35.793660+00:00 app[web.1]: TypeError: Cannot set property 'participants' of null\n2020-03-08T12:53:35.793660+00:00 app[web.1]:     at /app/services/requestService.js:672:30\n2020-03-08T12:53:35.793661+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:4779:16\n*/\n\n//  curl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"member\":\"ciao\"}' http://localhost:3000/68d4135a10e71f7bfa4dcf2f/requests/req123456/participants\nrouter.put('/:requestid/participants', async (req, res) => {\n  winston.debug(\"req.body\", req.body);\n\n  var participants = [];\n  req.body.forEach(function (participant, index) {\n    participants.push(participant);\n  });\n  winston.debug(\"var participants\", participants);\n\n  //setParticipantsByRequestId(request_id, id_project, participants)\n  return requestService.setParticipantsByRequestId(req.params.requestid, req.projectid, participants).then(function (updatedRequest) {\n\n    winston.debug(\"participant set\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch(function(err) {\n    winston.error(\"Error setting participants\", err);\n    return res.status(500).send({ success: false, error: \"Error setting participants\" });\n  });\n\n});\n\nrouter.put('/:requestid/replace', async (req, res) => {\n  \n  let id;\n  let name;\n  let slug;\n\n  if (req.body.id) {\n    id = \"bot_\" + req.body.id;\n  } else if (req.body.name) {\n    name = req.body.name;\n  } else if (req.body.slug) {\n    slug = req.body.slug;\n  } else {\n    return res.status(400).send({ success: false, error: \"Missing field 'id' or 'name' in body\" })\n  }\n\n  if (name) {\n    let chatbot = await faq_kb.findOne({ id_project: req.projectid, name: name, trashed: false }).catch((err) => {\n      winston.error(\"Error finding bot \", err);\n      return res.status(500).send({ success: false, error: \"An error occurred getting chatbot with name \" + name })\n    })\n\n    if (!chatbot) {\n      return res.status(404).send({ success: false, error: \"Chatbot with name '\" + name + \"' not found\" })\n    }\n\n    id = \"bot_\" + chatbot._id;\n    winston.verbose(\"Chatbot found: \", id);\n  }\n\n  if (slug) {\n    let chatbot = await faq_kb.findOne({ id_project: req.projectid, slug: slug}).catch((err) => {\n      winston.error(\"Error finding bot \", err);\n      return res.status(500).send({ success: false, error: \"An error occurred getting chatbot with slug \" + slug })\n    })\n\n    if (!chatbot) {\n      return res.status(404).send({ success: false, error: \"Chatbot with slug '\" + slug + \"' not found\" })\n    }\n\n    id = \"bot_\" + chatbot._id;\n    winston.verbose(\"Chatbot found: \" + id);\n  }\n\n  let participants = [];\n  participants.push(id);\n  winston.verbose(\"participants to be set: \", participants);\n\n  requestService.setParticipantsByRequestId(req.params.requestid, req.projectid, participants).then((updatedRequest) => {\n    winston.debug(\"SetParticipant response: \", updatedRequest);\n    res.status(200).send(updatedRequest);\n  }).catch((err) => {\n    winston.error(\"Error setting participants \", err);\n    res.status(500).send({ success: false, error: \"Error setting participants to request\"})\n  })\n})\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.delete('/:requestid/participants/:participantid', async (req, res) => {\n  winston.debug(req.body);\n  //removeParticipantByRequestId(request_id, id_project, member)\n  return requestService.removeParticipantByRequestId(req.params.requestid, req.projectid, req.params.participantid).then(function (updatedRequest) {\n\n    winston.verbose(\"participant removed\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch((err) => {\n    //winston.error(\"(Request) removeParticipantByRequestId error\", err)\n    return res.status(400).send({ success: false, error: \"Unable to remove the participant \" + req.params.participantid +  \" from the request \" + req.params.requestid})\n  });\n\n\n});\n\n// // TODO deprecated\n// router.delete('/:requestid/participants', function (req, res) {\n//   winston.debug(req.body);\n\n//    //removeParticipantByRequestId(request_id, id_project, member)\n//   return requestService.removeParticipantByRequestId(req.params.requestid, req.projectid, req.body.member ).then(function(updatedRequest) {\n\n//       winston.info(\"participant removed\", updatedRequest);\n\n//       return res.json(updatedRequest);\n//   });\n\n\n// });\n\n\n// router.put('/queue/:requestid/assign', function (req, res) { //fai altro route file\n//   winston.debug(req.body);\n// });\n\nrouter.put('/:requestid/assign', function (req, res) {\n  winston.debug(req.body);\n\n  let user = req.user;\n  let pu;\n  if (req.projectuser) {\n    pu = req.projectuser._id\n  }\n  // leggi la request se già assegnata o già chiusa (1000) esci \n\n  //cacheinvalidation\n  return Request.findOne({ \"request_id\": req.params.requestid, \"id_project\": req.projectid })\n    .exec(function (err, request) {\n\n      if (err) {\n        winston.error('Error patching request.', err);\n        return res.status(500).send({ success: false, msg: 'Error updating object.' });\n      }\n\n      if (!request) {\n        return res.status(404).send({ success: false, msg: 'Request not found' });\n      }\n\n      if (request.status === RequestConstants.ASSIGNED || request.status === RequestConstants.SERVED || request.status === RequestConstants.CLOSED) {\n        winston.info('Request already assigned');\n        return res.json(request);\n      }\n      //route(request_id, departmentid, id_project) {      \n      requestService.route(req.params.requestid, req.body.departmentid, req.projectid, req.body.nobot, req.body.no_populate).then(function (updatedRequest) {\n\n        winston.debug(\"department changed\", updatedRequest);\n\n        if (updatedRequest.status === RequestConstants.ABANDONED) {\n          eventService.emit('request.fully_abandoned', updatedRequest, req.projectid, pu, user._id, undefined, user)\n        }\n\n        return res.json(updatedRequest);\n      }).catch(function (error) {\n        // TODO: error log removed due to attempt to reduces logs when no department is found\n        console.log('Error changing the department.', error)\n        winston.verbose('Error changing the department.', error)\n        return res.status(500).send({ success: false, msg: 'Error changing the department.' });\n      })\n    });\n});\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.put('/:requestid/departments', function (req, res) {\n  winston.debug(req.body);\n  //route(request_id, departmentid, id_project) {      \n  requestService.route(req.params.requestid, req.body.departmentid, req.projectid, req.body.nobot, req.body.no_populate).then(function (updatedRequest) {\n\n    winston.debug(\"department changed\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch(function (error) {\n    // TODO: error log removed due to attempt to reduces logs when no department is found\n    console.log('Error changing the department.', error)\n    winston.verbose('Error changing the department.', error)\n    return res.status(500).send({ success: false, msg: 'Error changing the department.' });\n  })\n});\n\n\nrouter.put('/:requestid/agent', async (req, res) => {\n  winston.debug(req.body);\n  //route(request_id, departmentid, id_project) { \n\n\n  var request = await Request.findOne({ \"request_id\": req.params.requestid, id_project: req.projectid })\n    .exec();\n\n  if (!request) {\n    return res.status(404).send({ success: false, msg: 'Object not found.' });\n  }\n\n  var departmentid = request.department;\n  winston.debug(\"departmentid before: \" + departmentid);\n\n  if (!departmentid) {\n    var defaultDepartment = await departmentService.getDefaultDepartment(req.projectid);\n    winston.debug(\"defaultDepartment: \", defaultDepartment);\n    departmentid = defaultDepartment.id;\n  }\n  winston.debug(\"departmentid after: \" + departmentid);\n\n  requestService.route(req.params.requestid, departmentid, req.projectid, true, undefined).then(function (updatedRequest) {\n\n    winston.debug(\"department changed\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch(function (error) {\n    // TODO: error log removed due to attempt to reduces logs when no department is found\n    console.log('Error changing the department.', error)\n    winston.verbose('Error changing the department.', error)\n    return res.status(500).send({ success: false, msg: 'Error changing the department.' });\n  })\n\n\n});\n\n// router.post('/:requestid/attributes', function (req, res) {\n//   winston.debug(req.body);\n\n//   //return Request.findOneAndUpdate({\"request_id\":req.params.requestid},{ $push: { attributes: req.body } } , { new: true, upsert: false }, function (err, updatedMessage) {\n//     return Request.findOneAndUpdate({\"request_id\":req.params.requestid},{ $set: { attributes: req.body } } , { new: true, upsert: false }, function (err, updatedMessage) {\n//     if (err) {\n//       winston.error('Error patching request.', err);\n//       return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//     }\n//     requestEvent.emit(\"request.update\", updatedMessage);\n//     return res.json(updatedMessage);\n//   });\n\n// });\n\n// router.put('/:requestid/attributes/:attributeid', function (req, res) {\n//   winston.debug(req.body);\n\n//   return Request.findOneAndUpdate({\"request_id\":req.params.requestid, \"attributes._id\": req.params.attributeid},{ $set: { \"attributes.$\": req.body}} , { new: true, upsert: false }, function (err, updatedMessage) {\n//     if (err) {\n//       winston.error('Error patching request.', err);\n//       return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//     }\n//     requestEvent.emit(\"request.update\", updatedMessage);\n//     return res.json(updatedMessage);\n//   });\n\n// });\n\n// router.delete('/:requestid/attributes/:attributeid', function (req, res) {\n//   winston.debug(req.body);\n\n\n//   return Request.findOneAndUpdate({\"request_id\":req.params.requestid},{ \"$pull\": { \"attributes\": { \"_id\": req.params.attributeid } }} , { new: true, upsert: false }, function (err, updatedMessage) {\n//     if (err) {\n//       winston.error('Error patching request.', err);\n//       return res.status(500).send({ success: false, msg: 'Error updating object.' });\n//     }\n//     requestEvent.emit(\"request.update\", updatedMessage);\n//     return res.json(updatedMessage);\n//   });\n\n// });\n\n\nrouter.patch('/:requestid/attributes', function (req, res) {\n  var data = req.body;\n  var id_project = req.projectid;\n\n  // TODO use service method\n\n  Request.findOne({ \"request_id\": req.params.requestid, id_project: id_project })\n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')\n    .populate({ path: 'requester', populate: { path: 'id_user' } })\n    .exec(function (err, request) {\n      if (err) {\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      if (!request) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n\n\n      if (!request.attributes) {\n        winston.debug(\"empty attributes\")\n        request.attributes = {};\n      }\n\n      winston.debug(\" req attributes\", request.attributes)\n\n      Object.keys(data).forEach(function (key) {\n        var val = data[key];\n        winston.debug(\"data attributes \" + key + \" \" + val)\n        request.attributes[key] = val;\n      });\n\n      winston.debug(\" req attributes\", request.attributes)\n\n      // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n      request.markModified('attributes');\n\n      //cacheinvalidation\n      request.save(function (err, savedRequest) {\n        if (err) {\n          winston.error(\"error saving request attributes\", err)\n          return res.status(500).send({ success: false, msg: 'Error getting object.' });\n        }\n        winston.verbose(\" saved request attributes\", savedRequest.toObject())\n        requestEvent.emit(\"request.update\", savedRequest);\n        requestEvent.emit(\"request.update.comment\", { comment: \"ATTRIBUTES_PATCH\", request: savedRequest });//Deprecated\n        requestEvent.emit(\"request.updated\", { comment: \"ATTRIBUTES_PATCH\", request: savedRequest, patch: { attributes: data } });\n        requestEvent.emit(\"request.attributes.update\", savedRequest);\n        res.json(savedRequest);\n      });\n    });\n\n});\n\nrouter.post('/:requestid/notes', async function (req, res) {\n  \n  let request_id = req.params.requestid\n  var note = {};\n  note.text = req.body.text;\n  note.createdBy = req.user.id;\n\n  if (!note.text || note.text.trim() === '') {\n    return res.status(400).send({ success: false, error: \"Field 'text' is required. Received value: \" + note.text });\n  }\n\n  let project_user = req.projectuser;\n\n  if (project_user.role === RoleConstants.AGENT) {\n    let request = await Request.findOne({ request_id: request_id }).catch((err) => {\n      winston.error(\"Error finding request \", err);\n      return res.status(500).send({ success: false, error: \"Error finding request with id \" +  request_id });\n    })\n  \n    if (!request) {\n      winston.warn(\"Request with id \" + request_id + \" not found.\");\n      return res.status(404).send({ success: false, error: \"Request with id \" + request_id + \" not found.\"});\n    }\n\n    // Check if the user is a participant\n    // disable this check?\n    if (!request.participantsAgents.includes(req.user.id)) {\n      winston.verbose(\"Trying to add a note from a non participating agent\");\n      return res.status(403).send({ success: false, error: \"You are not participating in the conversation\"})\n    }\n  }\n\n  return Request.findOneAndUpdate({ request_id: request_id, id_project: req.projectid }, { $push: { notes: note } }, { new: true, upsert: false })\n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')\n    .populate({ path: 'requester', populate: { path: 'id_user' } })\n    .exec(function (err, updatedRequest) {\n\n      if (err) {\n        winston.error('Error adding request note.', err);\n        return res.status(500).send({ success: false, msg: 'Error adding request object.' });\n      }\n      requestEvent.emit(\"request.update\", updatedRequest);\n      requestEvent.emit(\"request.update.comment\", { comment: \"NOTE_ADD\", request: updatedRequest });//Deprecated\n      requestEvent.emit(\"request.updated\", { comment: \"NOTE_ADD\", request: updatedRequest, patch: { notes: note } });\n\n      return res.json(updatedRequest);\n    });\n\n});\n\n\nrouter.delete('/:requestid/notes/:noteid', async function (req, res) {\n\n  let request_id = req.params.requestid\n  let note_id = req.params.noteid;\n  let project_user = req.projectuser;\n\n  if (project_user.role === RoleConstants.AGENT) {\n    let request = await Request.findOne({ request_id: request_id }).catch((err) => {\n      winston.error(\"Error finding request \", err);\n      return res.status(500).send({ success: false, error: \"Error finding request with id \" +  request_id });\n    })\n  \n    if (!request) {\n      winston.warn(\"Request with id \" + request_id + \" not found.\");\n      return res.status(404).send({ success: false, error: \"Request with id \" + request_id + \" not found.\"});\n    }\n  \n    // Check if the user is a participant\n    // disable this check?\n    if (!request.participantsAgents.includes(req.user.id)) {\n      winston.verbose(\"Trying to delete a note from a non participating agent\");\n      return res.status(403).send({ success: false, error: \"You are not participating in the conversation\"})\n    }\n  }\n\n  //cacheinvalidation\n  return Request.findOneAndUpdate({ request_id: request_id, id_project: req.projectid }, { $pull: { notes: { \"_id\": note_id } } }, { new: true, upsert: false })\n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')\n    .populate({ path: 'requester', populate: { path: 'id_user' } })\n    .exec(function (err, updatedRequest) {\n\n      if (err) {\n        winston.error('Error adding request note.', err);\n        return res.status(500).send({ success: false, msg: 'Error adding request object.' });\n      }\n      requestEvent.emit(\"request.update\", updatedRequest);\n      requestEvent.emit(\"request.update.comment\", { comment: \"NOTE_DELETE\", request: updatedRequest });//Deprecated\n      // requestEvent.emit(\"request.updated\", {comment:\"NOTE_DELETE\",request:updatedRequest, patch:  {notes:req.params.noteid}});\n\n      return res.json(updatedRequest);\n    });\n\n});\n\n\n\n//TODO add cc\nrouter.post('/:requestid/email/send',\n  async (req, res) => {\n\n\n    let text = req.body.text;\n    winston.debug(\"text: \" + text);\n\n    let request_id = req.params.requestid;\n    winston.debug(\"request_id: \" + request_id);\n\n    let subject = req.body.subject;\n    winston.debug(\"subject: \" + subject);\n\n    winston.debug(\"req.project\", req.project);\n\n    let replyto = req.body.replyto;\n    winston.debug(\"replyto: \" + replyto);\n\n\n    let q = Request.findOne({ request_id: request_id, id_project: req.projectid })\n      // .select(\"+snapshot.agents\")\n      .populate('lead')\n    q.exec(function (err, request) {\n      if (err) {\n        winston.error(\"error getting request by id \", err);\n        return res.status(500).send({ success: false, msg: 'Error getting object.' });\n      }\n      if (!request) {\n        return res.status(404).send({ success: false, msg: 'Object not found.' });\n      }\n\n\n\n      winston.debug(\"Sending an email with text : \" + text + \" to request_id \" + request_id);\n\n      if (!request.lead.email) {\n        res.json({ \"no queued\": true });\n      }\n\n      let newto = request.lead.email\n      winston.verbose(\"Sending an email newto \" + newto);\n\n      //sendEmailDirect(to, text, project, request_id, subject, tokenQueryString, sourcePage, payload)\n      emailService.sendEmailDirect(newto, text, req.project, request_id, subject, undefined, undefined, undefined, replyto);\n\n      res.json({ \"queued\": true });\n\n\n    });\n\n\n\n\n  });\n\n\n\n\nrouter.post('/:requestid/followers',\n  [\n    check('member').notEmpty(),\n  ],\n  function (req, res) {\n    winston.info(\"followers add\", req.body);\n\n    const errors = validationResult(req);\n    if (!errors.isEmpty()) {\n      return res.status(422).json({ errors: errors.array() });\n    }\n\n    //addParticipantByRequestId(request_id, id_project, member)\n    return requestService.addFollowerByRequestId(req.params.requestid, req.projectid, req.body.member).then(function (updatedRequest) {\n\n      winston.verbose(\"participant added\", updatedRequest);\n\n      return res.json(updatedRequest);\n    }).catch(function(err) {\n      winston.error(\"Error adding follower\", err);\n      return res.status(500).send({ success: false, error: \"Error adding follower\" });\n    });\n\n  });\n\n\nrouter.put('/:requestid/followers', function (req, res) {\n  winston.debug(\"req.body\", req.body);\n\n  var followers = [];\n  req.body.forEach(function (follower, index) {\n    followers.push(follower);\n  });\n  winston.debug(\"var followers\", followers);\n\n  // setFollowersByRequestId(request_id, id_project, newfollowers)\n  return requestService.setFollowersByRequestId(req.params.requestid, req.projectid, followers).then(function (updatedRequest) {\n\n    winston.debug(\"followers set\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch(function(err) {\n    winston.error(\"Error setting followers\", err);\n    return res.status(500).send({ success: false, error: \"Error setting followers\" });\n  });\n\n});\n\n\nrouter.put('/:requestid/tag', async (req, res) => {\n\n  let id_project = req.projectid;\n  let request_id = req.params.requestid;\n  let tags_list = req.body;\n  winston.debug(\"(Request) /tag tags_list: \", tags_list)\n\n  if (tags_list.length == 0) {\n    winston.warn(\"(Request) /tag no tag specified\")\n    return res.status(400).send({ success: false, message: \"No tag specified\" })\n  }\n\n  let request = await Request.findOne({ id_project: id_project, request_id: request_id }).catch((err) => {\n    winston.error(\"(Request) /tag error getting request \", err);\n    return res.status(500).send({ success: false, error: \"Error getting request with request id \" + request_id});\n  })\n\n  if (!request) {\n    winston.warn(\"(Request) /tag request not found with request_id \" + request_id);\n    return res.status(404).send({ success: false, error: \"Request not found with request id \" + request_id});\n  }\n\n  let current_tags = request.tags;\n  let adding_tags = [];\n\n  tags_list.forEach(t => {\n    // Check if tag already exists in the conversation. If true, skip the adding.\n    if(!current_tags.some(tag => tag.tag === t.tag)) {\n      current_tags.push(t);\n      adding_tags.push(t);\n    }\n  })\n\n  let update = {\n    tags: current_tags\n  }\n\n  Request.findOneAndUpdate({ id_project: id_project, request_id: request_id }, update, { new: true }).then( async (updatedRequest) => {\n\n    if (!updatedRequest) {\n      winston.warn(\"(Request) /tag The request was deleted while adding tags for request \" + request_id);\n      return res.status(404).send({ success: false, error: \"The request was deleted while adding tags for request \" + request_id })\n    }\n\n    winston.debug(\"(Request) /tag Request updated successfully \", updatedRequest);\n\n    const populatedRequest = \n        await updatedRequest\n          .populate('lead')\n          .populate('department')\n          .populate('participatingBots')\n          .populate('participatingAgents')\n          .populate({ path: 'requester', populate: { path: 'id_user' } })  \n          .execPopulate();\n\n    requestEvent.emit(\"request.update\", populatedRequest)\n    res.status(200).send(updatedRequest)\n    \n    if (process.env.NODE_ENV !== 'test') {\n      scheduleTags(id_project, adding_tags);\n    }\n\n  }).catch((err) => {\n    winston.error(\"(Request) /tag error finding and update request \", err);\n    return res.status(500).send({ success: false, error: \"Error updating request with id \" + request_id })\n  })\n\n})\n\nrouter.delete('/:requestid/followers/:followerid', function (req, res) {\n  winston.debug(req.body);\n\n  //removeFollowerByRequestId(request_id, id_project, member)\n  return requestService.removeFollowerByRequestId(req.params.requestid, req.projectid, req.params.followerid).then(function (updatedRequest) {\n\n    winston.verbose(\"follower removed\", updatedRequest);\n\n    return res.json(updatedRequest);\n  }).catch(function(err) {\n    winston.error(\"Error removing follower\", err);\n    return res.status(500).send({ success: false, error: \"Error removing follower\" });\n  });\n\n\n});\n\n\n\nrouter.delete('/:requestid/tag/:tag_id', async (req, res) => {\n\n  let id_project = req.projectid;\n  let request_id = req.params.requestid;\n  let tag_id = req.params.tag_id;\n  \n  \n\n  Request.findOneAndUpdate({ id_project: id_project, request_id: request_id }, { $pull: { tags: { _id: tag_id } } }, { new: true }).then( async (updatedRequest) => {\n    \n    if (!updatedRequest) {\n      winston.warn(\"(Request) /removetag request not found with id: \" + request_id)\n      return res.status(404).send({ success: false, error: \"Request not found with id \" + request_id})\n    }\n\n    winston.debug(\"(Request) /removetag updatedRequest: \", updatedRequest)\n\n    const populatedRequest = \n        await updatedRequest\n          .populate('lead')\n          .populate('department')\n          .populate('participatingBots')\n          .populate('participatingAgents')\n          .populate({ path: 'requester', populate: { path: 'id_user' } })  \n          .execPopulate();\n\n    requestEvent.emit(\"request.update\", populatedRequest)\n    res.status(200).send(updatedRequest);\n    \n  }).catch((err) => {\n    winston.error(\"(Request) /removetag error updating request: \", err)\n    res.status(500).send({ success: false, error: err })\n  })\n})\n\n\n// TODO make a synchronous chat21 version (with query parameter?) with request.support_group.created\nrouter.delete('/:requestid', function (req, res) {\n\n  var projectuser = req.projectuser;\n\n  // request_role_check\n  if (!projectuser.hasPermissionOrRole('request_delete', 'owner')){\n    return res.status(403).send({ success: false, msg: 'Unauthorized.' });\n  }\n\n  Message.deleteMany({ recipient: req.params.requestid }, function (err) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error deleting messages.' });\n    }\n    winston.verbose('Messages deleted for the recipient: ' + req.params.requestid);\n  });\n\n\n  Request.findOneAndDelete({ request_id: req.params.requestid }, function (err, request) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    if (!request) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n\n    winston.verbose('Request deleted with request_id: ' + req.params.requestid);\n\n    requestEvent.emit('request.delete', request);\n\n    res.json(request);\n\n  });\n\n  // Request.remove({ request_id: req.params.requestid }, function (err, request) {\n  //   if (err) {\n  //     winston.error('--- > ERROR ', err);\n  //     return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n  //   }\n\n  //   if (!request) {\n  //     return res.status(404).send({ success: false, msg: 'Object not found.' });\n  //   }\n\n  //   winston.verbose('Request deleted with request_id: ' + req.params.requestid);\n\n  //   requestEvent.emit('request.delete', request);\n\n  //   res.json(request);\n\n  // });\n});\n\n\n\nrouter.delete('/id/:id', function (req, res) {\n\n  var projectuser = req.projectuser;\n\n  // request_role_check\n  if (!projectuser.hasPermissionOrRole('request_delete', 'owner')){\n    return res.status(403).send({ success: false, msg: 'Unauthorized.' });\n  }\n\n  Request.remove({ _id: req.params.id }, function (err, request) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    if (!request) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n\n    winston.verbose('Request deleted with id: ' + req.params.id);\n\n    requestEvent.emit('request.delete', request);\n\n    res.json(request);\n\n  });\n});\n\n\n// curl -v -X GET -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 http://localhost:3000/68d4135a10e71f7bfa4dcf2f/requests\n\nrouter.get('/', function (req, res, next) {\n\n  const startExecTime = new Date();\n\n  winston.debug(\"req projectid\", req.projectid);\n  winston.debug(\"req.query.sort\", req.query.sort);\n  winston.debug('REQUEST ROUTE - QUERY ', req.query)\n\n  const DEFAULT_LIMIT = 40;\n  \n  var page = 0;\n  var limit = DEFAULT_LIMIT; // Number of request per page\n  var page = 0;\n  var skip = 0;\n  let statusArray = [];\n  var projectuser = req.projectuser;\n  let filterRangeField = req.query.filterRangeField || 'createdAt';\n\n  if (req.query.limit) {\n    limit = parseInt(req.query.limit);\n  }\n  if (limit > 100) {\n    limit = DEFAULT_LIMIT;\n  }\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  skip = page * limit;\n\n  // Default query\n  var query = { \"id_project\": req.projectid, \"status\": { $lt: 1000, $nin: [50, 150] }, preflight: false };\n\n  if (req.user instanceof Subscription) {\n    // All request \n    winston.debug(\"Subscription All request \");\n  } else if (projectuser.hasPermissionOrRole('request_read_all', [\"owner\", \"admin\"])) {\n    // All request \n    winston.debug(\"hasPermissionOrRole All request \");\n  } else if (projectuser.hasPermissionOrRole('request_read_group', [\"agent\"])) {\n\n    query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n\n  } \n  // else if (projectuser.hasPermissionOrRole('request_read_mine', [\"????\"])) {\n  //   query[\"participants\"] = req.user.id;\n  // }\n  else {\n     query[\"participants\"] = req.user.id;\n    // generate empty requests response\n  }\n\n\n  if (req.query.dept_id) {\n    query.department = req.query.dept_id;\n  }\n\n  if (req.query.requester_email) {\n    query[\"snapshot.lead.email\"] = req.query.requester_email;\n  }\n\n  if (req.query.full_text) {\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  if (req.query.phone) {\n    // Match by digit sequence so e.g. \"3456677888\" finds \"+393456677888\"\n    var phoneDigits = req.query.phone.replace(/\\D/g, '');\n    if (phoneDigits.length > 0) {\n      query[\"contact.phone\"] = new RegExp(phoneDigits);\n    }\n  }\n\n  var history_search = false;\n\n  // Multiple status management\n  if (req.query.status) {\n    if (req.query.status === 'all') {\n      delete query.status;\n    } else {\n      let statusArray = req.query.status.split(',').map(Number);\n      statusArray = statusArray.map(status => { return isNaN(status) ? null : status }).filter(status => status !== null)\n      if (statusArray.length > 0) {\n        query.status = {\n          $in: statusArray\n        }\n      } else {\n        delete query.status;\n      }\n    }\n    if (statusArray.length > 0) {\n      query.status = {\n        $in: statusArray\n      }\n    }\n  }\n\n  if (req.query.lead) {\n    query.lead = req.query.lead;\n  }\n\n  // USERS & BOTS\n  if (req.query.participant) {\n    query.participants = req.query.participant;\n  }\n\n  if (req.query.hasbot != undefined) {\n    query.hasBot = req.query.hasbot;\n  }\n\n  if (req.query.tags) {\n    query[\"tags.tag\"] = req.query.tags;\n  }\n\n  if (req.query.location) {\n    query.location = req.query.location;\n  }\n\n  if (req.query.ticket_id) {\n    query.ticket_id = req.query.ticket_id;\n  }\n\n  if (req.query.preflight && (req.query.preflight === 'true' || req.query.preflight === true)) {\n    //query.preflight = req.query.preflight;\n    delete query.preflight;\n  }\n\n  // if (req.query.request_id) {\n  //   console.log('req.query.request_id', req.query.request_id);\n  //   query.request_id = req.query.request_id;\n  // }\n\n  let timezone = req.query.timezone || 'Europe/Rome';\n  let queryDateRange = false;\n  let queryStartDate;\n  let queryEndDate;\n  /**\n   **! *** DATE RANGE  USECASE 1 ***\n   *  in the tiledesk dashboard's HISTORY PAGE\n   *  WHEN THE TRIAL IS EXIPIRED OR THE SUBSCIPTION IS NOT ACTIVE\n   *  THE SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS ARE DISABLED AND \n   *  ARE DISPLAYED ONLY THE REQUESTS OF THE LAST 14 DAYS\n   */\n  //fixato. secondo me qui manca un parentesi tonda per gli or\n  if (history_search === true && req.project && req.project.profile && ((req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false))) {\n    queryDateRange = true;    \n    queryStartDate = moment().subtract(14, \"days\").format(\"YYYY/MM/DD\");\n    queryEndDate = null;\n  }\n\n  /**\n    **! *** DATE RANGE  USECASE 2 ***\n    *  in the tiledesk dashboard's HISTORY PAGE \n    *  WHEN THE USER SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS\n    */\n\n  if (req.query.start_date || req.query.end_date) {\n    queryDateRange = true; \n    queryStartDate = req.query.start_date;\n    queryEndDate = req.query.end_date;\n  }\n  else if (req.query.start_date_time || req.query.end_date_time) {\n    queryDateRange = true; \n    queryStartDate = req.query.start_date_time;\n    queryEndDate = req.query.end_date_time;\n  }\n\n  if (queryDateRange) {\n    try {\n      let rangeQuery = datesUtil.createDateRangeQuery(queryStartDate, queryEndDate, timezone, filterRangeField);\n      Object.assign(query, rangeQuery);\n    } catch (error) {\n      winston.error('Error creating date range query: ', error);\n      return res.status(500).send({ success: false, error: error?.message });\n    }\n  }\n\n  if (req.query.snap_department_routing) {\n    query[\"snapshot.department.routing\"] = req.query.snap_department_routing;\n  }\n\n  if (req.query.snap_department_default) {\n    query[\"snapshot.department.default\"] = req.query.snap_department_default;\n  }\n\n\n  if (req.query.snap_department_id_bot) {\n    query[\"snapshot.department.id_bot\"] = req.query.snap_department_id_bot;\n  }\n\n  if (req.query.snap_department_id_bot_exists) {\n    query[\"snapshot.department.id_bot\"] = { \"$exists\": req.query.snap_department_id_bot_exists }\n  }\n\n  if (req.query.snap_lead_lead_id) {\n    query[\"snapshot.lead.lead_id\"] = req.query.snap_lead_lead_id;\n  }\n\n  if (req.query.snap_lead_email) {\n    query[\"snapshot.lead.email\"] = req.query.snap_lead_email;\n  }\n\n  if (req.query.smartAssignment) {\n    query.smartAssignment = req.query.smartAssignment;\n  }\n\n  if (req.query.channel) {\n    if (req.query.channel === \"offline\") {\n      query[\"channel.name\"] = { \"$in\": [\"email\", \"form\"] }\n    } else if (req.query.channel === \"online\") {\n      query[\"channel.name\"] = { \"$nin\": [\"email\", \"form\"] }\n    } else {\n      query[\"channel.name\"] = req.query.channel\n    }\n  }\n\n  if (req.query.workingStatus?.ne) {\n    query.workingStatus = { $ne: req.query.workingStatus.ne };\n  }\n\n  if (req.query.priority) {\n    query.priority = req.query.priority;\n  }\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  // VOICE FILTERS - Start\n  if (req.query.caller) {\n    query[\"attributes.caller_phone\"] = req.query.caller;\n  }\n  if (req.query.called) {\n    query[\"attributes.called_phone\"] = req.query.called;\n  }\n  if (req.query.call_id) {\n    query[\"attributes.call_id\"] = req.query.call_id;\n  }\n  // VOICE FILTERS - End\n\n  if (req.query.duration && req.query.duration_op) {\n    let duration = Number(req.query.duration) * 60 * 1000;\n    if (req.query.duration_op === 'gt') {\n      query.duration = { $gte: duration }\n    } else if (req.query.duration_op === 'lt') {\n      query.duration = { $lte: duration }\n    } else {\n      winston.verbose(\"Duration operator can be 'gt' or 'lt'. Skip duration_op \" + req.query.duration_op)\n    }\n  }\n\n  if (req.query.abandonded && (req.query.abandoned === true || req.query.abandoned === 'true')) {\n    query[\"attributes.fully_abandoned\"] = true\n  }\n\n  if (req.query.rated && (req.query.rated === true || req.query.rated === 'true')) {\n    query.rating = { $exists: true }\n  }\n\n  if (req.query.draft && (req.query.draft === 'false' || req.query.draft === false)) {\n    query.draft = { $in: [false, null] }\n  }\n\n  let inWStatus = req.query.workingStatus?.in?.split(',').map(s => s.trim()).filter(Boolean);\n  let ninWStatus = req.query.workingStatus?.nin?.split(',').map(s => s.trim()).filter(Boolean);\n\n  if (ninWStatus && ninWStatus.length > 0) {\n    if (ninWStatus.length === 1) {\n      query.workingStatus = { $ne: ninWStatus[0] };\n    } else {\n      query.workingStatus = { $nin: ninWStatus };\n    }\n  } else if (inWStatus && inWStatus.length > 0) {\n    if (inWStatus.length === 1) {\n      query.workingStatus = inWStatus[0];\n    } else {\n      query.workingStatus = { $in: inWStatus };\n    }\n  }\n\n  var projection = undefined;\n\n  if (req.query.full_text) {\n    if (req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n      winston.verbose('fulltext projection on');\n      projection = { score: { $meta: \"textScore\" } };\n    }\n  }\n\n  winston.verbose('REQUEST ROUTE - REQUEST FIND QUERY ', query);\n  \n  var q1 = Request.find(query, projection).\n    skip(skip).limit(limit);\n\n  if (req.query.no_populate != \"true\" && req.query.no_populate != true) {\n    q1.populate('department').\n      populate('participatingBots').            //nico già nn gli usa\n      populate('participatingAgents').          //nico già nn gli usa\n      populate('lead').\n      populate({ path: 'requester', populate: { path: 'id_user' } });        //toglilo perche nico lo prende già da snapshot\n  }\n\n  if (req.query.full_text) {\n    if (req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n      q1.sort({ score: { $meta: \"textScore\" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n    }\n  } else {\n    q1.sort(sortQuery);\n  }\n\n  q1.exec();\n\n  // TODO if ?onlycount=true do not perform find query but only \n  // set q1 to undefined; to skip query\n\n  var q2 = Request.countDocuments(query).exec();\n\n  if (req.query.no_count && req.query.no_count == \"true\") {\n    winston.info('REQUEST ROUTE - no_count ');\n    q2 = 0;\n  }\n\n  var promises = [ q1, q2 ];\n\n  Promise.all(promises).then(function (results) {\n    var objectToReturn = {\n      perPage: limit,\n      count: results[1],\n      requests: results[0]\n    };\n    winston.debug('REQUEST ROUTE - objectToReturn ');\n    winston.debug('REQUEST ROUTE - objectToReturn ', objectToReturn);\n\n    const endExecTime = new Date();\n    winston.verbose('REQUEST ROUTE - exec time:  ' + (endExecTime - startExecTime));\n\n    return res.json(objectToReturn);\n\n  }).catch(function (err) {\n    winston.error('REQUEST ROUTE - REQUEST FIND ERR ', err);\n    return res.status(500).send({ success: false, msg: 'Error getting requests.', err: err });\n  });\n\n\n});\n\n// router.get('/', function (req, res, next) {\n\n//   const startExecTime = new Date();\n\n//   winston.debug(\"req projectid\", req.projectid);\n//   winston.debug(\"req.query.sort\", req.query.sort);\n//   winston.debug('REQUEST ROUTE - QUERY ', req.query)\n\n//   const DEFAULT_LIMIT = 40;\n\n//   var limit = DEFAULT_LIMIT; // Number of request per page\n\n//   if (req.query.limit) {\n//     limit = parseInt(req.query.limit);\n//   }\n//   if (limit > 100) {\n//     limit = DEFAULT_LIMIT;\n//   }\n\n\n//   var page = 0;\n\n//   if (req.query.page) {\n//     page = req.query.page;\n//   }\n\n//   var skip = page * limit;\n//   winston.debug('REQUEST ROUTE - SKIP PAGE ', skip);\n\n\n//   var query = { \"id_project\": req.projectid, \"status\": { $lt: 1000 }, preflight: false };\n\n//   var projectuser = req.projectuser;\n\n\n//   if (req.user instanceof Subscription) {\n//     //all request \n//   } else if (projectuser && (projectuser.role == \"owner\" || projectuser.role == \"admin\")) {\n//     //all request \n//     // per uni mostrare solo quelle priprio quindi solo participants\n//     if (req.query.mine) {\n//       query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n//     }\n//   } else {\n//     query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n//   }\n\n//   // console.log('REQUEST ROUTE - req ', req); \n//   // console.log('REQUEST ROUTE - req.project ', req.project); \n\n\n//   if (req.query.dept_id) {\n//     query.department = req.query.dept_id;\n//     winston.debug('REQUEST ROUTE - QUERY DEPT ID', query.department);\n//   }\n\n//   if (req.query.requester_email) {\n//     query[\"snapshot.lead.email\"] = req.query.requester_email;\n//   }\n\n//   if (req.query.full_text) {\n//     winston.debug('req.query.fulltext', req.query.full_text);\n//     query.$text = { \"$search\": req.query.full_text };\n//   }\n\n//   var history_search = false;\n\n//   if (req.query.status) {\n//     winston.debug('req.query.status', req.query.status);\n//     query.status = req.query.status;\n\n//     if (req.query.status == 1000 || req.query.status == \"1000\") {\n//       history_search = true;\n//     }\n//     if (req.query.status === \"all\") {\n//       history_search = true;\n//       delete query.status;\n//     }\n//   }\n\n//   if (req.query.lead) {\n//     winston.debug('req.query.lead', req.query.lead);\n//     query.lead = req.query.lead;\n//   }\n\n//   // USERS & BOTS\n//   if (req.query.participant) {\n//     winston.debug('req.query.participant', req.query.participant);\n//     query.participants = req.query.participant;\n//   }\n\n//   winston.debug('req.query.hasbot', req.query.hasbot);\n//   if (req.query.hasbot != undefined) {\n//     winston.debug('req.query.hasbot', req.query.hasbot);\n//     query.hasBot = req.query.hasbot;\n//   }\n\n//   // if (req.query.waiting_time_exists) { //non basta aggiungi anche che nn è null\n//   //   query.waiting_time = {\"$exists\": req.query.waiting_time_exists} //{$ne:null}\n//   //   winston.debug('REQUEST ROUTE - QUERY waiting_time_exists', query.waiting_time_exists);\n//   // }\n\n\n//   if (req.query.tags) {\n//     winston.debug('req.query.tags', req.query.tags);\n//     query[\"tags.tag\"] = req.query.tags;\n//   }\n\n//   if (req.query.location) {\n//     query.location = req.query.location;\n//   }\n\n//   if (req.query.ticket_id) {\n//     query.ticket_id = req.query.ticket_id;\n//   }\n\n//   if (req.query.preflight) {\n//     query.preflight = req.query.preflight;\n//   }\n\n//   // if (req.query.request_id) {\n//   //   console.log('req.query.request_id', req.query.request_id);\n//   //   query.request_id = req.query.request_id;\n//   // }\n\n//   /**\n//    **! *** DATE RANGE  USECASE 1 ***\n//    *  in the tiledesk dashboard's HISTORY PAGE\n//    *  WHEN THE TRIAL IS EXIPIRED OR THE SUBSCIPTION IS NOT ACTIVE\n//    *  THE SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS ARE DISABLED AND \n//    *  ARE DISPLAYED ONLY THE REQUESTS OF THE LAST 14 DAYS\n//    */\n//   //fixato. secondo me qui manca un parentesi tonda per gli or\n//   if (history_search === true && req.project && req.project.profile && ((req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false))) {\n\n\n//     var startdate = moment().subtract(14, \"days\").format(\"YYYY-MM-DD\");\n\n//     var enddate = moment().format(\"YYYY-MM-DD\");\n\n//     winston.debug('»»» REQUEST ROUTE - startdate ', startdate);\n//     winston.debug('»»» REQUEST ROUTE - enddate ', enddate);\n\n//     var enddatePlusOneDay = moment(new Date()).add(1, 'days').toDate()\n//     winston.debug('»»» REQUEST ROUTE - enddate + 1 days: ', enddatePlusOneDay);\n\n//     // var enddatePlusOneDay = \"2019-09-17T00:00:00.000Z\"\n\n//     query.createdAt = { $gte: new Date(Date.parse(startdate)).toISOString(), $lte: new Date(enddatePlusOneDay).toISOString() }\n//     winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);\n\n//   }\n\n//   /**\n//     **! *** DATE RANGE  USECASE 2 ***\n//     *  in the tiledesk dashboard's HISTORY PAGE \n//     *  WHEN THE USER SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS\n//     */\n//   if (req.query.start_date && req.query.end_date) {\n//     winston.debug('REQUEST ROUTE - REQ QUERY start_date ', req.query.start_date);\n//     winston.debug('REQUEST ROUTE - REQ QUERY end_date ', req.query.end_date);\n\n//     /**\n//      * USING TIMESTAMP  in MS    */\n//     // var formattedStartDate = new Date(+req.query.start_date);\n//     // var formattedEndDate = new Date(+req.query.end_date);\n//     // query.createdAt = { $gte: formattedStartDate, $lte: formattedEndDate }\n\n//     /**\n//      * USING MOMENT      */\n//     var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n//     var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n//     winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED START DATE ', startDate);\n//     winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE ', endDate);\n\n//     // ADD ONE DAY TO THE END DAY\n//     var date = new Date(endDate);\n//     var newdate = new Date(date);\n//     var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);\n//     winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);\n//     // var endDate_plusOneDay =   moment('2018-09-03').add(1, 'd')\n//     // var endDate_plusOneDay =   endDate.add(1).day();\n//     // var toDate = new Date(Date.parse(endDate_plusOneDay)).toISOString()\n\n//     query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }\n//     winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);\n\n//   } else if (req.query.start_date && !req.query.end_date) {\n//     winston.debug('REQUEST ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');\n//     var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n//     var range = { $gte: new Date(Date.parse(startDate)).toISOString() };\n//     if (req.query.filterRangeField) {\n//       query[req.query.filterRangeField] = range;\n//     } else {\n//       query.createdAt = range;\n//     }\n\n//     winston.debug('REQUEST ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);\n//   }\n//   // }\n\n\n\n//   if (req.query.snap_department_routing) {\n//     query[\"snapshot.department.routing\"] = req.query.snap_department_routing;\n//     winston.debug('REQUEST ROUTE - QUERY snap_department_routing', query.snap_department_routing);\n//   }\n\n//   if (req.query.snap_department_default) {\n//     query[\"snapshot.department.default\"] = req.query.snap_department_default;\n//     winston.debug('REQUEST ROUTE - QUERY snap_department_default', query.snap_department_default);\n//   }\n\n\n//   if (req.query.snap_department_id_bot) {\n//     query[\"snapshot.department.id_bot\"] = req.query.snap_department_id_bot;\n//     winston.debug('REQUEST ROUTE - QUERY snap_department_id_bot', query.snap_department_id_bot);\n//   }\n\n//   if (req.query.snap_department_id_bot_exists) {\n//     query[\"snapshot.department.id_bot\"] = { \"$exists\": req.query.snap_department_id_bot_exists }\n//     winston.debug('REQUEST ROUTE - QUERY snap_department_id_bot_exists', query.snap_department_id_bot_exists);\n//   }\n\n//   if (req.query.snap_lead_lead_id) {\n//     query[\"snapshot.lead.lead_id\"] = req.query.snap_lead_lead_id;\n//     winston.debug('REQUEST ROUTE - QUERY snap_lead_lead_id', query.snap_lead_lead_id);\n//   }\n\n//   if (req.query.snap_lead_email) {\n//     query[\"snapshot.lead.email\"] = req.query.snap_lead_email;\n//     winston.debug('REQUEST ROUTE - QUERY snap_lead_email', query.snap_lead_email);\n//   }\n\n//   if (req.query.smartAssignment) {\n//     query.smartAssignment = req.query.smartAssignment;\n//     winston.debug('REQUEST ROUTE - QUERY smartAssignment', query.smartAssignment);\n//   }\n\n//   if (req.query.channel) {\n//     if (req.query.channel === \"offline\") {\n//       query[\"channel.name\"] = { \"$in\": [\"email\", \"form\"] }\n//     } else if (req.query.channel === \"online\") {\n//       query[\"channel.name\"] = { \"$nin\": [\"email\", \"form\"] }\n//     } else {\n//       query[\"channel.name\"] = req.query.channel\n//     }\n\n//     winston.debug('REQUEST ROUTE - QUERY channel', query.channel);\n//   }\n\n//   if (req.query.priority) {\n//     query.priority = req.query.priority;\n//   }\n\n\n//   var direction = -1; //-1 descending , 1 ascending\n//   if (req.query.direction) {\n//     direction = req.query.direction;\n//   }\n//   winston.debug(\"direction\", direction);\n\n//   var sortField = \"createdAt\";\n//   if (req.query.sort) {\n//     sortField = req.query.sort;\n//   }\n//   winston.debug(\"sortField\", sortField);\n\n//   var sortQuery = {};\n//   sortQuery[sortField] = direction;\n//   winston.debug(\"sort query\", sortQuery);\n\n//   if (req.query.draft && (req.query.draft === 'false' || req.query.draft === false)) {\n//     query.draft = { $in: [false, null] }\n//   }\n\n//   winston.debug('REQUEST ROUTE - REQUEST FIND ', query);\n\n//   var projection = undefined;\n\n//   if (req.query.full_text) {\n\n//     if (req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n//       winston.info('fulltext projection on');\n//       projection = { score: { $meta: \"textScore\" } };\n//     }\n\n//   }\n//   // requestcachefarequi populaterequired\n//   var q1 = Request.find(query, projection).\n//     skip(skip).limit(limit);\n\n\n\n\n\n//   winston.debug('REQUEST ROUTE no_populate:' + req.query.no_populate);\n\n//   if (req.query.no_populate != \"true\" && req.query.no_populate != true) {\n//     winston.verbose('REQUEST ROUTE - no_polutate false ', req.headers);\n//     q1.populate('department').\n//       populate('participatingBots').            //nico già nn gli usa\n//       populate('participatingAgents').          //nico già nn gli usa\n//       populate('lead').\n//       populate({ path: 'requester', populate: { path: 'id_user' } });        //toglilo perche nico lo prende già da snapshot\n//   }\n\n//   // cache(cacheUtil.defaultTTL, \"requests-\"+projectId).    \n\n\n//   // if (req.query.select_snapshot) {\n//   //   winston.info('select_snapshot');\n//   //   q1.select(\"+snapshot\");\n//   //   // q1.select({ \"snapshot\": 1});\n//   // }\n\n//   if (req.query.full_text) {\n//     winston.debug('fulltext sort');\n//     if (req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n//       q1.sort({ score: { $meta: \"textScore\" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n//     }\n//   } else {\n//     q1.sort(sortQuery);\n//   }\n\n\n//   // winston.info('q1',q1);\n\n\n//   q1.exec();\n\n//   // TODO if ?onlycount=true do not perform find query but only \n//   // set q1 to undefined; to skip query\n\n//   var q2 = Request.countDocuments(query).exec();\n\n//   if (req.query.no_count && req.query.no_count == \"true\") {\n//     winston.info('REQUEST ROUTE - no_count ');\n//     q2 = 0;\n//   }\n\n//   var promises = [\n//     q1,\n//     q2\n//   ];\n\n//   Promise.all(promises).then(function (results) {\n//     var objectToReturn = {\n//       perPage: limit,\n//       count: results[1],\n//       requests: results[0]\n//     };\n//     winston.debug('REQUEST ROUTE - objectToReturn ');\n//     winston.debug('REQUEST ROUTE - objectToReturn ', objectToReturn);\n\n//     const endExecTime = new Date();\n//     winston.verbose('REQUEST ROUTE - exec time:  ' + (endExecTime - startExecTime));\n\n//     return res.json(objectToReturn);\n\n//   }).catch(function (err) {\n//     winston.error('REQUEST ROUTE - REQUEST FIND ERR ', err);\n//     return res.status(500).send({ success: false, msg: 'Error getting requests.', err: err });\n//   });\n\n\n// });\n\n\n// TODO converti con fast-csv e stream\n// DOWNLOAD HISTORY REQUESTS AS CSV\nrouter.get('/csv', function (req, res, next) {\n\n  winston.debug(\"req projectid\", req.projectid);\n  winston.debug(\"req.query.sort\", req.query.sort);\n  winston.debug('REQUEST ROUTE - QUERY ', req.query)\n\n  var limit = 100000; // Number of request per page\n  var page = 0;\n  var skip = 0;\n  let statusArray = [];\n  var projectuser = req.projectuser;\n  let filterRangeField = req.query.filterRangeField || 'createdAt';\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  skip = page * limit;\n  winston.debug('REQUEST ROUTE - SKIP PAGE ', skip);\n\n  // Default query (same as GET /)\n  var query = { \"id_project\": req.projectid, \"status\": { $lt: 1000, $nin: [50, 150] }, preflight: false };\n\n  if (req.user instanceof Subscription) {\n    // All request\n  } else if (projectuser && (projectuser.role == \"owner\" || projectuser.role == \"admin\")) {\n    if (req.query.mine) {\n      query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n    }\n  } else {\n    query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n  }\n\n  if (req.query.dept_id) {\n    query.department = req.query.dept_id;\n    winston.debug('REQUEST ROUTE - QUERY DEPT ID', query.department);\n  }\n\n  if (req.query.requester_email) {\n    query[\"snapshot.lead.email\"] = req.query.requester_email;\n  }\n\n  if (req.query.full_text) {\n    winston.debug('req.query.fulltext', req.query.full_text);\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  if (req.query.phone) {\n    var phoneDigits = req.query.phone.replace(/\\D/g, '');\n    if (phoneDigits.length > 0) {\n      query[\"contact.phone\"] = new RegExp(phoneDigits);\n    }\n  }\n\n  var history_search = false;\n\n  // Multiple status management (same as GET /)\n  if (req.query.status) {\n    if (req.query.status === 'all') {\n      delete query.status;\n    } else {\n      statusArray = req.query.status.split(',').map(Number);\n      statusArray = statusArray.map(status => { return isNaN(status) ? null : status }).filter(status => status !== null);\n      if (statusArray.length > 0) {\n        query.status = { $in: statusArray };\n      } else {\n        delete query.status;\n      }\n    }\n  }\n\n  if (req.query.lead) {\n    winston.debug('req.query.lead', req.query.lead);\n    query.lead = req.query.lead;\n  }\n\n  // USERS & BOTS\n  if (req.query.participant) {\n    winston.debug('req.query.participant', req.query.participant);\n    query.participants = req.query.participant;\n  }\n\n  winston.debug('req.query.hasbot', req.query.hasbot);\n  if (req.query.hasbot != undefined) {\n    winston.debug('req.query.hasbot', req.query.hasbot);\n    query.hasBot = req.query.hasbot;\n  }\n\n  if (req.query.tags) {\n    query[\"tags.tag\"] = req.query.tags;\n  }\n\n  if (req.query.location) {\n    query.location = req.query.location;\n  }\n\n  if (req.query.ticket_id) {\n    query.ticket_id = req.query.ticket_id;\n  }\n\n  if (req.query.preflight && (req.query.preflight === 'true' || req.query.preflight === true)) {\n    delete query.preflight;\n  }\n\n  let timezone = req.query.timezone || 'Europe/Rome';\n  let queryDateRange = false;\n  let queryStartDate;\n  let queryEndDate;\n\n  if (history_search === true && req.project && req.project.profile && ((req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false))) {\n    queryDateRange = true;\n    queryStartDate = moment().subtract(14, \"days\").format(\"YYYY/MM/DD\");\n    queryEndDate = null;\n  }\n\n  if (req.query.start_date || req.query.end_date) {\n    queryDateRange = true;\n    queryStartDate = req.query.start_date;\n    queryEndDate = req.query.end_date;\n  } else if (req.query.start_date_time || req.query.end_date_time) {\n    queryDateRange = true;\n    queryStartDate = req.query.start_date_time;\n    queryEndDate = req.query.end_date_time;\n  }\n\n  if (queryDateRange) {\n    try {\n      let rangeQuery = datesUtil.createDateRangeQuery(queryStartDate, queryEndDate, timezone, filterRangeField);\n      Object.assign(query, rangeQuery);\n    } catch (error) {\n      winston.error('Error creating date range query: ', error);\n      return res.status(500).send({ success: false, error: error?.message });\n    }\n  }\n\n  if (req.query.snap_department_routing) {\n    query[\"snapshot.department.routing\"] = req.query.snap_department_routing;\n  }\n\n  if (req.query.snap_department_default) {\n    query[\"snapshot.department.default\"] = req.query.snap_department_default;\n  }\n\n  if (req.query.snap_department_id_bot) {\n    query[\"snapshot.department.id_bot\"] = req.query.snap_department_id_bot;\n  }\n\n  if (req.query.snap_department_id_bot_exists) {\n    query[\"snapshot.department.id_bot\"] = { \"$exists\": req.query.snap_department_id_bot_exists };\n  }\n\n  if (req.query.snap_lead_lead_id) {\n    query[\"snapshot.lead.lead_id\"] = req.query.snap_lead_lead_id;\n  }\n\n  if (req.query.snap_lead_email) {\n    query[\"snapshot.lead.email\"] = req.query.snap_lead_email;\n  }\n\n  if (req.query.smartAssignment) {\n    query.smartAssignment = req.query.smartAssignment;\n  }\n\n  if (req.query.channel) {\n    if (req.query.channel === \"offline\") {\n      query[\"channel.name\"] = { \"$in\": [\"email\", \"form\"] };\n    } else if (req.query.channel === \"online\") {\n      query[\"channel.name\"] = { \"$nin\": [\"email\", \"form\"] };\n    } else {\n      query[\"channel.name\"] = req.query.channel;\n    }\n  }\n\n  if (req.query.priority) {\n    query.priority = req.query.priority;\n  }\n\n  var direction = -1; // same default as GET /\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  winston.debug(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  winston.debug(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n  winston.debug(\"sort query\", sortQuery);\n\n  // VOICE FILTERS\n  if (req.query.caller) {\n    query[\"attributes.caller_phone\"] = req.query.caller;\n  }\n  if (req.query.called) {\n    query[\"attributes.called_phone\"] = req.query.called;\n  }\n  if (req.query.call_id) {\n    query[\"attributes.call_id\"] = req.query.call_id;\n  }\n\n  if (req.query.duration && req.query.duration_op) {\n    let duration = Number(req.query.duration) * 60 * 1000;\n    if (req.query.duration_op === 'gt') {\n      query.duration = { $gte: duration };\n    } else if (req.query.duration_op === 'lt') {\n      query.duration = { $lte: duration };\n    } else {\n      winston.verbose(\"Duration operator can be 'gt' or 'lt'. Skip duration_op \" + req.query.duration_op);\n    }\n  }\n\n  if (req.query.abandonded && (req.query.abandoned === true || req.query.abandoned === 'true')) {\n    query[\"attributes.fully_abandoned\"] = true;\n  }\n\n  if (req.query.rated && (req.query.rated === true || req.query.rated === 'true')) {\n    query.rating = { $exists: true };\n  }\n\n  if (req.query.draft && (req.query.draft === 'false' || req.query.draft === false)) {\n    query.draft = { $in: [false, null] };\n  }\n\n  var csvProjection = '-transcript -status -__v';\n  if (req.query.full_text && req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n    winston.verbose('fulltext projection on');\n    csvProjection = { transcript: 0, status: 0, __v: 0, score: { $meta: \"textScore\" } };\n  }\n\n  winston.debug(\"csv query\", query);\n  winston.debug('REQUEST ROUTE - REQUEST FIND ', query);\n\n  var q = Request.find(query, csvProjection).\n    skip(skip).limit(limit).\n    populate('department').\n    populate('lead').\n    lean();\n\n  if (req.query.full_text && req.query.no_textscore != \"true\" && req.query.no_textscore != true) {\n    q.sort({ score: { $meta: \"textScore\" } });\n  } else {\n    q.sort(sortQuery);\n  }\n\n  return q.exec(function (err, requests) {\n      if (err) {\n        winston.error('REQUEST ROUTE - REQUEST FIND ERR ', err)\n        return res.status(500).send({ success: false, msg: 'Error getting csv requests.', err: err });\n      }\n\n\n      requests.forEach(function (element) {\n\n        var channel_name = \"\";\n        if (element.channel && element.channel.name) {\n          channel_name = element.channel.name;\n        }\n        delete element.channel;\n        element.channel_name = channel_name;\n\n        var department_name = \"\";\n        if (element.department && element.department.name) {\n          department_name = element.department.name;\n        }\n        delete element.department;\n        element.department_name = department_name;\n\n        var lead_fullname = \"\";\n        if (element.lead && element.lead.fullname) {\n          lead_fullname = element.lead.fullname\n        }\n        element.lead_fullname = lead_fullname;\n\n\n        var lead_email = \"\";\n        if (element.lead && element.lead.email) {\n          lead_email = element.lead.email\n        }\n\n        element.lead_email = lead_email;\n\n        var tags = [];\n        var tagsString = \"\";\n        if (element.tags && element.tags.length > 0) {\n\n          element.tags.forEach(function (tag) {\n            // tags = tags  + tag.tag + \", \";\n            tags.push(tag.tag);\n          });\n        }\n        tagsString = tags.join(\", \")\n\n        winston.debug('tagsString ' + tagsString)\n\n        element.tags = tagsString;\n\n\n        // var participatingAgents = \"\";\n        // if (element.participatingAgents && element.participatingAgents.length>0) {\n        //   element.participatingAgents.forEach(function(agent) {                \n        //     participatingAgents = participatingAgents + \", \" + agent;\n        //   });          \n        // }\n        // // da terminare e testare. potrebbe essere troppo lenta la query per tanti record\n        // element.participatingAgents = participatingAgents;\n\n        if (element.attributes) {\n          if (element.attributes.caller_phone) {\n            element.caller_phone = element.attributes.caller_phone;\n          }\n          if (element.attributes.called_phone) {\n            element.called_phone = element.attributes.called_phone;\n          }\n          if (element.attributes.caller_phone) {\n            element.call_id = element.attributes.call_id;\n          }\n        }\n\n        delete element.lead;\n\n        delete element.attributes;\n\n        delete element.notes;\n\n        // delete element.tags;\n\n        delete element.channelOutbound;\n\n        delete element.location;\n\n        delete element.snapshot;\n\n\n        // TODO print also lead. use a library to flattize\n      });\n\n      winston.debug('REQUEST ROUTE - REQUEST AS CSV', requests);\n\n      return res.csv(requests, true);\n    });\n\n  // });\n\n});\n\nrouter.get('/count', async (req, res) => {\n\n  let id_project = req.projectid;\n  let merge_assigned = req.query.merge_assigned;\n  let quota_count = false;\n  if (req.query.conversation_quota === true || req.query.conversation_quota === 'true') {\n    quota_count = true;\n  }\n\n  let open_count = 0;\n  let closed_count = 0;\n  let unassigned_count = 0;\n  let human_assigned_count = 0;\n  let bot_assigned_count = 0;\n\n  let currentSlot\n  let promises = [];\n\n  if (quota_count) {\n\n    let quoteManager = req.app.get('quote_manager');\n\n    currentSlot = await quoteManager.getCurrentSlot(req.project);\n    winston.debug(\"currentSlot: \", currentSlot)\n    // Open count\n    // Warning: 201 is a fake status -> Logical meaning: status < 1000;\n    // (method) RequestService.getConversationsCount(id_project: any, status: any, preflight: any, hasBot: any): Promise<any>\n    promises.push(requestService.getConversationsCount(id_project, 201, null, null, currentSlot.startDate, currentSlot.endDate).then((count) => {\n      open_count = count;\n      winston.debug(\"Unassigned count for project \" + id_project + \": \" + unassigned_count);\n    }).catch((err) => {\n      winston.error(\"Error getting unassigned conversation count\");\n    }))\n\n    // Closed count\n    promises.push(requestService.getConversationsCount(id_project, RequestConstants.CLOSED, null, null, currentSlot.startDate, currentSlot.endDate).then((count) => {\n      closed_count = count;\n      winston.debug(\"Unassigned count for project \" + id_project + \": \" + unassigned_count);\n    }).catch((err) => {\n      winston.error(\"Error getting unassigned conversation count\");\n    }))\n  } else {\n    // Unassigned count\n    promises.push(requestService.getConversationsCount(id_project, RequestConstants.UNASSIGNED, false, null, null, null).then((count) => {\n      unassigned_count = count;\n      winston.debug(\"Unassigned count for project \" + id_project + \": \" + unassigned_count);\n    }).catch((err) => {\n      winston.error(\"Error getting unassigned conversation count\");\n    }))\n  \n    // Human assigned count\n    promises.push(requestService.getConversationsCount(id_project, RequestConstants.ASSIGNED, false, false, null, null).then((count) => {\n      human_assigned_count = count;\n      winston.debug(\"Human assigned count for project \" + id_project + \": \" + human_assigned_count);\n    }).catch((err) => {\n      winston.error(\"Error getting human assigned conversation count\");\n    }))\n  \n    // Bot assigned count\n    promises.push(requestService.getConversationsCount(id_project, RequestConstants.ASSIGNED, false, true, null, null).then((count) => {\n      bot_assigned_count = count;\n      winston.debug(\"Bot assigned count for project \" + id_project + \": \" + bot_assigned_count);\n    }).catch((err) => {\n      winston.error(\"Error getting bot assigned conversation count\");\n    }))\n  }\n\n  Promise.all(promises).then((response) => {\n\n    if (quota_count) {\n      let data = {\n        open: open_count,\n        closed: closed_count,\n        slot: {\n          startDate: currentSlot.startDate.format('DD/MM/YYYY'),\n          endDate: currentSlot.endDate.format('DD/MM/YYYY')\n        }\n      }\n      return res.status(200).send(data);\n    } else {\n      if (merge_assigned) {\n        let data = {\n          unassigned: unassigned_count,\n          assigned: human_assigned_count + bot_assigned_count\n        }\n        return res.status(200).send(data);\n  \n      } else {\n        let data = {\n          unassigned: unassigned_count,\n          assigned: human_assigned_count,\n          bot_assigned: bot_assigned_count\n        }\n        return res.status(200).send(data);\n      }\n    }\n\n  }).catch((err) => {\n    console.error(\"err: \", err);\n    return res.status(400).send({ success: false, error: err });\n  })\n})\n\n// router.get('/count', async (req, res) => {\n  \n//   let id_project = req.projectid;\n//   let merge_assigned = req.query.merge_assigned;\n\n//   let unassigned_count = 0;\n//   let human_assigned_count = 0;\n//   let bot_assigned_count = 0;\n\n//   unassigned_count = await Request.countDocuments({ id_project: id_project, status: 100, preflight: false }).catch((err) => {\n//     winston.error(\"Error getting unassigned requests count: \", err);\n//     res.status(400).send({ success: false, error: err });\n//   })\n//   winston.debug(\"Unassigned count for project \" + id_project + \": \" + unassigned_count);\n\n//   human_assigned_count = await Request.countDocuments({ id_project: id_project, status: 200, preflight: false, hasBot: false }).catch((err) => {\n//     winston.error(\"Error getting human unassigned requests count: \", err);\n//     res.status(400).send({ success: false, error: err });\n//   })\n//   winston.debug(\"Human assigned count for project \" + id_project + \": \" + human_assigned_count);\n\n//   bot_assigned_count = await Request.countDocuments({ id_project: id_project, status: 200, preflight: false, hasBot: true }).catch((err) => {\n//     winston.error(\"Error getting bot assigned requests count: \", err);\n//     res.status(400).send({ success: false, error: err });\n//   })\n//   winston.debug(\"Bot assigned count for project \" + id_project + \": \" + bot_assigned_count);\n\n\n//   if (merge_assigned) {\n//     let data = {\n//       unassigned: unassigned_count,\n//       assigned: human_assigned_count + bot_assigned_count\n//     }\n//     return res.status(200).send(data);\n    \n//   } else {\n//     let data = {\n//       unassigned: unassigned_count,\n//       assigned: human_assigned_count,\n//       bot_assigned: bot_assigned_count\n//     }\n//     return res.status(200).send(data);\n//   }\n\n// })\n\nrouter.get('/:requestid', function (req, res) {\n\n  var requestid = req.params.requestid;\n  winston.debug(\"get request by id: \" + requestid);\n\n\n  let q = Request.findOne({ request_id: requestid, id_project: req.projectid })\n    // .select(\"+snapshot.agents\")\n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')\n    .populate({ path: 'requester', populate: { path: 'id_user' } });\n\n  // if (cacheEnabler.request) {  cache disabled beacuse cacheoose don't support .populate without lean. here cache is not important\n  //   q.cache(cacheUtil.defaultTTL, req.projectid+\":requests:request_id:\"+requestid) //request_cache\n  //   winston.debug('request cache enabled');\n  // }\n  // \n  //  .populate({path:'requester',populate:{path:'id_user', select:{'firstname':1, 'lastname':1}}})\n  q.exec(function (err, request) {\n    if (err) {\n      winston.error(\"error getting request by id \", err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!request) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(request);\n  });\n});\n\nrouter.get('/:requestid/chatbot/parameters', async (req, res) => {\n\n  let project_id = req.projectid;\n  let request_id = req.params.requestid;\n\n  let split_pattern = /-/\n  let splitted = request_id.split(split_pattern);\n  \n  if (project_id !== splitted[2]) {\n    return res.status(401).send({ success: false, message: \"Request does not belong to the project\"})\n  }\n\n  requestService.getRequestParametersFromChatbot(request_id).then((parameters) => {\n    res.status(200).send(parameters);\n\n  }).catch((err) => {\n    console.error(\"err: \", err.response)\n    res.status(400).send(err);\n  })\n\n})\n\n\nasync function scheduleTags(id_project, tags) {\n  \n  let scheduler = new Scheduler({ jobManager: jobManager })\n  tags.forEach(t => {\n    let payload = {\n      id_project: id_project,\n      tag: t.tag,\n      type: \"conversation-tag\",\n      date: new Date()\n    }\n    scheduler.tagSchedule(payload, async (err, result) => {\n      if (err) {\n        winston.error(\"Scheduling error: \", err);\n      } else {\n        winston.verbose(\"Scheduling result: \", result);\n      }\n    })\n  })\n}\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/requestUtilRoot.js",
    "content": "var express = require('express');\n\nvar router = express.Router();\n\nvar Request = require(\"../models/request\");\nvar winston = require('../config/winston');\n\n// https://tiledesk-server-pre.herokuapp.com/requests_util/lookup/id_project/support-group-60ffe291f725db00347661ef-b4cb6875785c4a23b27244fe498eecf4\nrouter.get('/lookup/id_project/:request_id',  function(req, res) {\n  winston.debug(\"lookup: \"+req.params.request_id);\n  \n  return Request.findOne({request_id: req.params.request_id}).select(\"id_project\").exec(function(err, request) { \n      if (err) {\n        return res.status(500).send({success: false, msg: 'Error creating message', err:err });\n      } \n      if (!request) {\n        return res.status(404).send({success: false, msg: \"Request with  \" + req.params.request_id + \" not found\" });\n      }\n      winston.debug(\"request\",request);\n      res.json({id_project: request.id_project});\n    });\n\n});\n\n\nmodule.exports = router;\n\n\n\n\n"
  },
  {
    "path": "routes/roles.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Role = require(\"../models/role\");\nvar winston = require('../config/winston');\nconst roleEvent = require('../event/roleEvent');\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var newRole = new Role({\n              name: req.body.name,\n              permissions: req.body.permissions,              \n              id_project: req.projectid,\n              createdBy: req.user.id,\n              updatedBy: req.user.id\n            });\n          \n  newRole.save(function(err, savedRole) {\n      if (err) {\n        winston.error('Error saving the role '+ JSON.stringify(newRole), err)\n        return res.status(500).send({ success: false, msg: 'Error inserting object.' });\n      }            \n\n      roleEvent.emit('role.create', savedRole);\n      \n      return res.json(savedRole);\n  })\n\n\n});\n\nrouter.put('/:roleid', function (req, res) {\n  winston.debug(req.body);\n  var update = {};\n  \n  if (req.body.name!=undefined) {\n    update.name = req.body.name;\n  }\n  \n  if (req.body.permissions!=undefined) {\n    update.permissions = req.body.permissions;\n  }\n   \n  \n  Role.findByIdAndUpdate(req.params.roleid, update, { new: true, upsert: true }, function (err, updatedRole) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n    \n    roleEvent.emit('role.update', updatedRole);\n\n    res.json(updatedRole);\n  });\n});\n\n\n\nrouter.delete('/:roleid', function (req, res) {\n  winston.debug(req.body);\n\n  // Role.remove({ _id: req.params.roleid }, function (err, role) {\n  Role.findOneAndDelete({  _id: req.params.roleid }, (err, role) => {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n    roleEvent.emit('role.delete', role);\n\n    res.json(role);\n  });\n});\n\n\nrouter.get('/:roleid', function (req, res) {\n  winston.debug(req.body);\n\n  Role.findById(req.params.roleid, function (err, role) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!role) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(role);\n  });\n});\n\n\nrouter.get('/', async(req, res) => {\n\n  var limit = 40; // Number of request per page\n\n  if (req.query.limit) {    \n    limit = parseInt(req.query.limit);\n    winston.debug('LEAD ROUTE - limit: '+limit);\n  }\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('ROLE ROUTE - SKIP PAGE ', skip);\n\n\n  var query = {\"id_project\": req.projectid};\n\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n\n\n  return Role.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, roles) {\n      if (err) {\n        winston.error('ROLE ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n\n      return res.json(roles);\n      \n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/segment.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\nvar Segment = require(\"../models/segment\");\n\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var newSegment = new Segment({\n    name: req.body.name,\n    match: req.body.match,\n    filters: req.body.filters,\n    id_project: req.projectid,    \n    createdBy: req.user.id\n  });\n\n  newSegment.save(function(err, segment) {\n    if (err) {\n      winston.error('Error saving the segment '+ JSON.stringify(segment), err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n\n    }            \n    winston.verbose('segment created ', segment.toJSON());\n\n   \n    res.json(segment);\n  })\n\n});\n\nrouter.put('/:segmentid', function (req, res) {\n  winston.debug(req.body);\n  var update = {};\n  \n    if (req.body.name!=undefined) {\n      update.name = req.body.name;\n    }\n   \n    if (req.body.match!=undefined) {\n      update.match = req.body.match;\n    }\n    if (req.body.filters!=undefined) {\n      update.filters = req.body.filters;\n    }\n    \n\n\n    Segment.findByIdAndUpdate(req.params.segmentid, update, { new: true, upsert: true }, function (err, updatedSegment) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    res.json(updatedSegment);\n  });\n});\n\n\n\nrouter.delete('/:segmentid', function (req, res) {\n  winston.debug(req.body);\n\n  Segment.findByIdAndUpdate(req.params.segmentid, {status:0}, { new: true, upsert: true }, function (err, updatedSegment) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    res.json(updatedSegment);\n  });\n});\n\n\nrouter.get('/:segmentid', function (req, res) {\n  winston.debug(req.body);\n\n  Segment.findById(req.params.segmentid, function (err, segment) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!segment) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(segment);\n  });\n});\n\n\nrouter.get('/', function (req, res) {\n\n  var limit = 40; // Number of request per page\n\n  if (req.query.limit) {    \n    limit = parseInt(req.query.limit);\n    winston.debug('LEAD ROUTE - limit: '+limit);\n  }\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('LEAD ROUTE - SKIP PAGE ', skip);\n\n\n  var query = { \"id_project\": req.projectid, \"status\": 100};\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  \n\n  return Segment.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, segments) {\n      if (err) {\n        winston.error('segments ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n\n      // blocked to 1000 TODO increases it\n      //  collection.count is deprecated, and will be removed in a future version. Use Collection.countDocuments or Collection.estimatedDocumentCount instead\n      return Segment.countDocuments(query, function (err, totalRowCount) {\n\n        var objectToReturn = {\n          perPage: limit,\n          count: totalRowCount,\n          segments: segments\n        };\n\n        return res.json(objectToReturn);\n      });\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/setting.js",
    "content": "var express = require('express');\nvar router = express.Router();\n// var Setting = require(\"../models/setting\");\n\n\n\n// router.get('/', function (req, res) {\n//   console.log('Setting')\n//   Setting.findOne({}).\n//     exec(function (err, setting) {  \n//       res.json(setting);\n//     });\n// });\n\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/subscription.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Subscription = require(\"../models/subscription\");\nvar SubscriptionLog = require(\"../models/subscriptionLog\");\nvar subscriptionEvent = require(\"../event/subscriptionEvent\");\nvar winston = require('../config/winston');\n\n//space\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var subscription = new Subscription({\n    target: req.body.target,\n    event: req.body.event,\n    id_project: req.projectid,\n    createdBy: req.user.id\n  });\n\n  subscription.save(function (err, subscriptionSaved) {\n    if (err) {\n      if (err.code === 11000) { //error for dupes\n        return Subscription.findOne({id_project:req.projectid, event: req.body.event}).select(\"+secret\")\n                  .exec(function (err, subscriptionSaved) {\n          res.setHeader('x-hook-secret', subscriptionSaved.secret);\n          res.json(subscriptionSaved);\n        });\n      }    \n      winston.error('--- > ERROR ', err)\n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n    // http://resthooks.org/docs/security/\n\n    res.setHeader('x-hook-secret', subscriptionSaved.secret);\n\n    subscriptionEvent.emit('subscription.create', subscriptionSaved );\n    res.json(subscriptionSaved);\n  });\n});\n\n\nrouter.post('/test', function (req, res) {\n\n  winston.debug(\"test subscription body\", req.body);\n  \n  res.json(req);\n});\n\nrouter.put('/:subscriptionid', function (req, res) {\n\n  winston.debug(req.body);\n\n  Subscription.findByIdAndUpdate(req.params.subscriptionid, req.body, { new: true, upsert: true }, function (err, subscriptionUpd) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n    subscriptionEvent.emit('subscription.update', subscriptionUpd );\n    res.json(subscriptionUpd);\n  });\n});\n\n\nrouter.delete('/:subscriptionid', function (req, res) {\n\n  winston.debug(req.body);\n\n  Subscription.findByIdAndRemove(req.params.subscriptionid, { new: false}, function (err, subscriptionUpd) {\n    // Subscription.remove({ _id: req.params.subscriptionid }, function (err, subscriptionUpd) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n    subscriptionEvent.emit('subscription.delete', subscriptionUpd );\n    res.json(subscriptionUpd);\n  });\n});\n\n\n\n\nrouter.get('/', function (req, res) {\n\n  return Subscription.find({ \"id_project\": req.projectid }).\n    exec(function (err, subscriptions, next) {\n      if (err) {\n        winston.error('Subscription ROUTE - REQUEST FIND ERR ', err)\n        return next(err);\n      }\n\n        return res.json(subscriptions);\n      });\n  \n});\n\nrouter.get('/history', function (req, res) {\n\n  var limit = 40; // Number of leads per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('Subscription ROUTE - SKIP PAGE ', skip);\n\n\n  return SubscriptionLog.find({ \"id_project\": req.projectid }).\n    skip(skip).limit(limit)\n    .sort({createdAt: 'desc'}).\n    exec(function (err, subscriptions, next) {\n      if (err) {\n        winston.error('Subscription ROUTE - REQUEST FIND ERR ', err)\n        return next(err);\n      }\n\n        return res.json(subscriptions);\n      });\n  \n});\n\n\nrouter.get('/:subscriptionid', function (req, res) {\n\n  winston.debug(req.body);\n\n  Subscription.findById(req.params.subscriptionid, function (err, subscriptionUpd) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!subscriptionUpd) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(subscriptionUpd);\n  });\n});\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/tag.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar TagLibrary = require(\"../models/tagLibrary\");\nvar winston = require('../config/winston');\n\nrouter.post('/', function (req, res) {\n\n  winston.debug(req.body);\n  winston.debug(\"req.user\", req.user);\n\n  var newTag = new TagLibrary({\n    tag: req.body.tag,  \n    color: req.body.color,\n    id_project: req.projectid,\n    createdBy: req.user.id,\n    updatedBy: req.user.id\n  });\n\n  newTag.save(function (err, savedTag) {\n    if (err) {\n      // winston.error('--- > ERROR ', err)    \n      if (err.code === 11000) { //error for dupes\n          return TagLibrary.findOne({id_project:req.projectid, tag: req.body.tag },function (err, savedTag) {\n            res.json(savedTag);\n          });\n      }    \n      winston.error('--- > ERROR ', err)          \n      return res.status(500).send({ success: false, msg: 'Error saving object.' });\n    }\n\n    res.json(savedTag);\n  });\n});\n\nrouter.put('/:tagid', function (req, res) {\n  winston.debug(req.body);\n  var update = {};\n  \n\n  update.tag = req.body.tag;   \n  update.color = req.body.color;\n  \n  \n  TagLibrary.findByIdAndUpdate(req.params.tagid, update, { new: true, upsert: true }, function (err, updatedTag) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n  \n\n    // TagEvent.emit('Tag.update', updatedTag);\n    res.json(updatedTag);\n  });\n});\n\nrouter.delete('/:tagid', function (req, res) {\n  winston.debug(req.body);\n\n  TagLibrary.remove({ _id: req.params.tagid }, function (err, tag) {\n    if (err) {\n      winston.error('--- > ERROR ', err);\n      return res.status(500).send({ success: false, msg: 'Error deleting object.' });\n    }\n\n  \n    // TagEvent.emit('Tag.delete', Tag);\n\n    res.json(tag);\n  });\n});\n\nrouter.get('/:tagid', function (req, res) {\n  winston.debug(req.body);\n\n  TagLibrary.findById(req.params.tagid, function (err, tag) {\n    if (err) {\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!tag) {\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    res.json(tag);\n  });\n});\n\nrouter.get('/', function (req, res) {\n  var limit = 40; // Number of Tags per page\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('Tag ROUTE - SKIP PAGE ', skip);\n\n\n  var query = { \"id_project\": req.projectid};\n\n  // if (req.query.full_text) {\n  //   winston.debug('Tag ROUTE req.query.fulltext', req.query.full_text);\n  //   query.$text = { \"$search\": req.query.full_text };\n  // }\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  return TagLibrary.find(query).\n    skip(skip).limit(limit).\n    sort(sortQuery).\n    exec(function (err, tags) {\n      if (err) {\n        winston.error('Tag ROUTE - REQUEST FIND ERR ', err)\n        return (err);\n      }\n    \n        return res.json(tags);\n    });\n});\n\n\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/troubleshooting.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\n\nrouter.get('/headers', function(req, res) {\n  winston.info(\"req.headers\", req.headers);\n  // TODO chech if query is null\n  res.json(req.headers);\n\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/unanswered.js",
    "content": "const express = require('express');\nconst router = express.Router();\nconst { Namespace, UnansweredQuestion } = require('../models/kb_setting');\nvar winston = require('../config/winston');\n\n// Add a new unanswered question\nrouter.post('/', async (req, res) => {\n    try {\n        const { namespace, question, request_id, sender } = req.body;\n        const id_project = req.projectid;\n\n        if (!namespace || !question) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameters: namespace and question\"\n            });\n        }\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n\n        const unansweredQuestion = new UnansweredQuestion({\n            id_project,\n            namespace,\n            question,\n            request_id,\n            sender\n        });\n\n        const savedQuestion = await unansweredQuestion.save();\n        res.status(200).json(savedQuestion);\n\n    } catch (error) {\n        winston.error('Error adding unanswered question:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error adding unanswered question\"\n        });\n    }\n});\n\n// Get all unanswered questions for a namespace\nrouter.get('/:namespace', async (req, res) => {\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        if (!namespace) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameter: namespace\"\n            });\n        }\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n\n        const page = parseInt(req.query.page) || 0;\n        const limit = parseInt(req.query.limit) || 20;\n        const sortField = req.query.sortField || 'createdAt';\n        const direction = parseInt(req.query.direction) || -1;\n\n        const filter = { id_project, namespace };\n\n        let projection = undefined;\n\n        if (req.query.search) {\n            filter.$text = { $search: req.query.search };\n            // Add score to projection if it's a text search\n            projection = { score: { $meta: \"textScore\" } };\n        }\n\n        let sortObj;\n        if (projection && projection.score) {\n            sortObj = { score: { $meta: \"textScore\" } };\n        } else {\n            sortObj = { [sortField]: direction };\n        }\n\n        const questions = await UnansweredQuestion.find(filter, projection)\n            .sort(sortObj)\n            .skip(page * limit)\n            .limit(limit);\n\n        const count = await UnansweredQuestion.countDocuments(filter);\n\n        res.status(200).json({\n            count,\n            questions,\n            query: {\n                page,\n                limit,\n                sortField,\n                direction,\n                search: req.query.search || undefined\n            }\n        });\n\n    } catch (error) {\n        winston.error('Error getting unanswered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error getting unanswered questions\"\n        });\n    }\n});\n\n// Delete a specific unanswered question\nrouter.delete('/:id', async (req, res) => {\n    try {\n        const { id } = req.params;\n        const id_project = req.projectid;\n\n        const deleted = await UnansweredQuestion.findOneAndDelete({ _id: id, id_project });\n        if (!deleted) {\n            return res.status(404).json({\n                success: false,\n                error: \"Question not found\"\n            });\n        }\n\n        res.status(200).json({\n            success: true,\n            message: \"Question deleted successfully\"\n        });\n        \n    } catch (error) {\n        winston.error('Error deleting unanswered question:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error deleting unanswered question\"\n        });\n    }\n});\n\n// Delete all unanswered questions for a namespace\nrouter.delete('/namespace/:namespace', async (req, res) => {\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n\n        const result = await UnansweredQuestion.deleteMany({ id_project, namespace });\n        res.status(200).json({\n            success: true,\n            count: result.deletedCount,\n            message: \"All questions deleted successfully\"\n        });\n\n    } catch (error) {\n        winston.error('Error deleting unanswered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error deleting unanswered questions\"\n        });\n    }\n});\n\n// Update an unanswered question\nrouter.put('/:id', async (req, res) => {\n    try {\n        const { id } = req.params;\n        const { question } = req.body;\n        const id_project = req.projectid;\n\n        if (!question) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameter: question\"\n            });\n        }\n\n        const updatedQuestion = await UnansweredQuestion.findOneAndUpdate(\n            { _id: id, id_project },\n            { question },\n            { new: true }\n        );\n\n        if (!updatedQuestion) {\n            return res.status(404).json({\n                success: false,\n                error: \"Question not found\"\n            });\n        }\n\n        res.status(200).json(updatedQuestion);\n\n    } catch (error) {\n        winston.error('Error updating unanswered question:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error updating unanswered question\"\n        });\n    }\n});\n\n// Count unanswered questions for a namespace\nrouter.get('/count/:namespace', async (req, res) => {\n    try {\n        const { namespace } = req.params;\n        const id_project = req.projectid;\n\n        if (!namespace) {\n            return res.status(400).json({\n                success: false,\n                error: \"Missing required parameter: namespace\"\n            });\n        }\n\n        // Check if namespace belongs to project\n        const isValidNamespace = await validateNamespace(id_project, namespace);\n        if (!isValidNamespace) {\n            return res.status(403).json({\n                success: false,\n                error: \"Not allowed. The namespace does not belong to the current project.\"\n            });\n        }\n\n        const count = await UnansweredQuestion.countDocuments({\n            id_project,\n            namespace\n        });\n\n        res.status(200).json({ count });\n\n    } catch (error) {\n        winston.error('Error counting unanswered questions:', error);\n        res.status(500).json({\n            success: false,\n            error: \"Error counting unanswered questions\"\n        });\n    }\n});\n\n// Helper function to validate namespace\nasync function validateNamespace(id_project, namespace_id) {\n    try {\n        const namespace = await Namespace.findOne({ \n            id_project: id_project,\n            id: namespace_id \n        });\n        return !!namespace; // return true if namespace exists, false otherwise\n    } catch (err) {\n        winston.error(\"validate namespace error: \", err);\n        throw err;\n    }\n}\n\nmodule.exports = router; "
  },
  {
    "path": "routes/urls.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\n\n\nrouter.get('/redirect', function (req, res) {\n  winston.debug(\"redirect: \"+ req.query.path);\n  res.redirect(req.query.path);\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/user-request.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Request = require(\"../models/request\");\nvar winston = require('../config/winston');\nvar moment = require('moment');\nconst requestEvent = require('../event/requestEvent');\nconst { check, validationResult } = require('express-validator');\nvar requestService = require('../services/requestService');\nvar mongoose = require('mongoose');\n\n\nrouter.patch('/:requestid/rating', function (req, res) {\n  winston.debug(req.body);\n  const update = {};\n\n  if (req.body.rating) {\n    update.rating = req.body.rating;\n  }\n\n  if (req.body.rating_message) {\n    update.rating_message = req.body.rating_message;\n  }\n  \n  winston.debug(\"Request user patch update\",update);\n\n  let query = {\n    request_id: req.params.requestid\n  }\n\n  if (req.projectuser) {\n    query.requester = req.projectuser.id\n  }\n\n    //cacheinvalidation\n  return Request.findOneAndUpdate(query, { $set: update }, { new: true, upsert: false })\n  .populate('lead')\n  .populate('department')\n  .populate('participatingBots')\n  .populate('participatingAgents')  \n  .populate({path:'requester',populate:{path:'id_user'}})\n  .exec( function(err, request) {\n       \n    if (err) {\n      winston.error('Error user patching request.', err);\n      return res.status(500).send({ success: false, msg: 'Error updating object.' });\n    }\n\n    if (!request) {\n      return res.status(404).send({ success: false, msg: 'Request not found' });\n    }\n\n    requestEvent.emit(\"request.update\", request);\n    requestEvent.emit(\"request.update.comment\", {comment:\"PATCH\",request:request});//Deprecated\n    requestEvent.emit(\"request.updated\", {comment:\"PATCH\",request:request, patch:  update});\n\n    return res.json(request);\n  });\n\n});\n\n\nrouter.put('/:requestid/closeg', function (req, res) {\n  winston.debug(req.body);\n  \n    // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by)\n    const closed_by = req.user.id;\n  return requestService.closeRequestByRequestId(req.params.requestid, req.projectid, false, true, closed_by).then(function(closedRequest) {\n\n      winston.verbose(\"request closed\", closedRequest);\n\n        return res.json(closedRequest);\n      \n  }).catch(function(err) {\n    winston.error(\"Error closing request\", err);\n    return res.status(500).send({ success: false, error: \"Error closing request\" });\n  });\n\n\n});\n\n\n\n\nrouter.get('/me', function (req, res, next) {\n\n  winston.debug(\"req projectid\", req.projectid);\n  winston.debug(\"req.query.sort\", req.query.sort);\n  winston.debug('REQUEST ROUTE - QUERY ', req.query)\n\n  const DEFAULT_LIMIT = 40;\n\n  var limit = DEFAULT_LIMIT; // Number of request per page\n\n  if (req.query.limit) {\n    limit = parseInt(req.query.limit);\n  }\n  if (limit > 100) {\n    limit = DEFAULT_LIMIT;\n  }\n\n\n  var page = 0;\n\n  if (req.query.page) {\n    page = req.query.page;\n  }\n\n  var skip = page * limit;\n  winston.debug('REQUEST ROUTE - SKIP PAGE ', skip);\n\n\n  var user_id = req.user._id;\n  winston.debug('REQUEST ROUTE - user_id:  '+user_id);\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(user_id);\n  winston.debug(\"isObjectId:\"+ isObjectId);\n\n\n  var query = { \"id_project\": req.projectid, \"status\": {$lt:1000} };\n  \n  if (!(req.query.preflight === \"true\" || req.query.preflight === true)) {\n    query.preflight = false;\n  }\n\n  if (isObjectId) {\n    query[\"snapshot.requester.id_user\"] = user_id;\n    // query.id_user = mongoose.Types.ObjectId(contact_id);\n  } else {\n    query[\"snapshot.requester.uuid_user\"] = user_id;\n  }\n\n\n  // $or:[{\"snapshot.requester.id_user\": user_id}, {\"snapshot.requester.uuid_user\": user_id}]};  \n\n  winston.debug('REQUEST ROUTE - query ', query);\n\n\n  if (req.query.dept_id) {\n    query.department = req.query.dept_id;\n    winston.debug('REQUEST ROUTE - QUERY DEPT ID', query.department);\n  }\n\n  if (req.query.full_text) {\n    winston.debug('req.query.fulltext', req.query.full_text);\n    query.$text = { \"$search\": req.query.full_text };\n  }\n\n  var history_search = false;\n\n  if (req.query.status) {\n    winston.debug('req.query.status', req.query.status);\n    query.status = req.query.status;\n\n    if (req.query.status == 1000 || req.query.status == \"1000\") {\n      history_search = true;\n    }\n    if (req.query.status===\"all\") {\n      history_search = true;\n      delete query.status;\n    }\n  }\n\n  // if (req.query.lead) {\n  //   winston.debug('req.query.lead', req.query.lead);\n  //   query.lead = req.query.lead;\n  // }\n\n  // USERS & BOTS\n  if (req.query.participant) {\n    winston.debug('req.query.participant', req.query.participant);\n    query.participants = req.query.participant;\n  }\n\n  winston.debug('req.query.hasbot', req.query.hasbot);\n  if (req.query.hasbot!=undefined) {\n    winston.debug('req.query.hasbot', req.query.hasbot);\n    query.hasBot = req.query.hasbot;\n  }\n\n  // if (req.query.waiting_time_exists) { //non basta aggiungi anche che nn è null\n  //   query.waiting_time = {\"$exists\": req.query.waiting_time_exists} //{$ne:null}\n  //   winston.debug('REQUEST ROUTE - QUERY waiting_time_exists', query.waiting_time_exists);\n  // }\n\n\n  if (req.query.tags) {\n    winston.debug('req.query.tags', req.query.tags);\n    query[\"tags.tag\"] = req.query.tags;\n  }\n\n  if (req.query.location) {\n    query.location = req.query.location;\n  }\n\n  if (req.query.ticket_id) {\n    query.ticket_id = req.query.ticket_id;\n  }\n\n  // if (req.query.request_id) {\n  //   console.log('req.query.request_id', req.query.request_id);\n  //   query.request_id = req.query.request_id;\n  // }\n\n  /**\n   **! *** DATE RANGE  USECASE 1 ***\n   *  in the tiledesk dashboard's HISTORY PAGE\n   *  WHEN THE TRIAL IS EXIPIRED OR THE SUBSCIPTION IS NOT ACTIVE\n   *  THE SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS ARE DISABLED AND \n   *  ARE DISPLAYED ONLY THE REQUESTS OF THE LAST 14 DAYS\n   */\n  if ( history_search === true && req.project && req.project.profile && (req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false)) {\n\n\n    var startdate = moment().subtract(14, \"days\").format(\"YYYY-MM-DD\");\n\n    var enddate = moment().format(\"YYYY-MM-DD\");\n\n    winston.debug('»»» REQUEST ROUTE - startdate ', startdate);\n    winston.debug('»»» REQUEST ROUTE - enddate ', enddate);\n\n    var enddatePlusOneDay=  moment(new Date()).add(1, 'days').toDate()\n    winston.debug('»»» REQUEST ROUTE - enddate + 1 days: ', enddatePlusOneDay);\n\n    // var enddatePlusOneDay = \"2019-09-17T00:00:00.000Z\"\n\n    query.createdAt = { $gte: new Date(Date.parse(startdate)).toISOString(), $lte: new Date(enddatePlusOneDay).toISOString() }\n    winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);\n\n  }\n \n /**\n   **! *** DATE RANGE  USECASE 2 ***\n   *  in the tiledesk dashboard's HISTORY PAGE \n   *  WHEN THE USER SEARCH FOR DATE INTERVAL OF THE HISTORY OF REQUESTS\n   */\n  if (req.query.start_date && req.query.end_date) {\n    winston.debug('REQUEST ROUTE - REQ QUERY start_date ', req.query.start_date);\n    winston.debug('REQUEST ROUTE - REQ QUERY end_date ', req.query.end_date);\n\n    /**\n     * USING TIMESTAMP  in MS    */\n    // var formattedStartDate = new Date(+req.query.start_date);\n    // var formattedEndDate = new Date(+req.query.end_date);\n    // query.createdAt = { $gte: formattedStartDate, $lte: formattedEndDate }\n\n    /**\n     * USING MOMENT      */\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n    var endDate = moment(req.query.end_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED START DATE ', startDate);\n    winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE ', endDate);\n\n    // ADD ONE DAY TO THE END DAY\n    var date = new Date(endDate);\n    var newdate = new Date(date);\n    var endDate_plusOneDay = newdate.setDate(newdate.getDate() + 1);\n    winston.debug('REQUEST ROUTE - REQ QUERY FORMATTED END DATE + 1 DAY ', endDate_plusOneDay);\n    // var endDate_plusOneDay =   moment('2018-09-03').add(1, 'd')\n    // var endDate_plusOneDay =   endDate.add(1).day();\n    // var toDate = new Date(Date.parse(endDate_plusOneDay)).toISOString()\n\n    query.createdAt = { $gte: new Date(Date.parse(startDate)).toISOString(), $lte: new Date(endDate_plusOneDay).toISOString() }\n    winston.debug('REQUEST ROUTE - QUERY CREATED AT ', query.createdAt);\n\n  } else if (req.query.start_date && !req.query.end_date) {\n    winston.debug('REQUEST ROUTE - REQ QUERY END DATE IS EMPTY (so search only for start date)');\n    var startDate = moment(req.query.start_date, 'DD/MM/YYYY').format('YYYY-MM-DD');\n\n    var range = { $gte: new Date(Date.parse(startDate)).toISOString() };\n    if (req.query.filterRangeField) {\n      query[req.query.filterRangeField] = range;\n    }else {\n      query.createdAt = range;\n    }\n    \n    winston.debug('REQUEST ROUTE - QUERY CREATED AT (only for start date)', query.createdAt);\n  }\n  // }\n\n\n\n  if (req.query.snap_department_routing) {\n    query[\"snapshot.department.routing\"] = req.query.snap_department_routing;\n    winston.debug('REQUEST ROUTE - QUERY snap_department_routing', query.snap_department_routing);\n  }\n\n  if (req.query.snap_department_default) {\n    query[\"snapshot.department.default\"] = req.query.snap_department_default;\n    winston.debug('REQUEST ROUTE - QUERY snap_department_default', query.snap_department_default);\n  }\n\n\n  if (req.query.snap_department_id_bot) {\n    query[\"snapshot.department.id_bot\"] = req.query.snap_department_id_bot;\n    winston.debug('REQUEST ROUTE - QUERY snap_department_id_bot', query.snap_department_id_bot);\n  }\n\n  if (req.query.snap_department_id_bot_exists) {\n    query[\"snapshot.department.id_bot\"] = {\"$exists\": req.query.snap_department_id_bot_exists}\n    winston.debug('REQUEST ROUTE - QUERY snap_department_id_bot_exists', query.snap_department_id_bot_exists);\n  }\n\n  // if (req.query.snap_lead_lead_id) {\n  //   query[\"snapshot.lead.lead_id\"] = req.query.snap_lead_lead_id;\n  //   winston.debug('REQUEST ROUTE - QUERY snap_lead_lead_id', query.snap_lead_lead_id);\n  // }\n\n  if (req.query.channel) {\n    query[\"channel.name\"] =  req.query.channel\n    winston.debug('REQUEST ROUTE - QUERY channel', query.channel);\n  }\n\n\n  var direction = -1; //-1 descending , 1 ascending\n  if (req.query.direction) {\n    direction = req.query.direction;\n  }\n  winston.debug(\"direction\", direction);\n\n  var sortField = \"createdAt\";\n  if (req.query.sort) {\n    sortField = req.query.sort;\n  }\n  winston.debug(\"sortField\", sortField);\n\n  var sortQuery = {};\n  sortQuery[sortField] = direction;\n\n  winston.debug(\"sort query\", sortQuery);\n\n  winston.verbose('REQUEST ROUTE - REQUEST FIND ', query);\n\n  var projection = undefined;\n\n  if (req.query.full_text) {  \n    winston.debug('fulltext projection'); \n\n    projection = {score: { $meta: \"textScore\" } };\n  }\n  // requestcachefarequi populaterequired\n  var q1 = Request.find(query, projection).\n    skip(skip).limit(limit);\n\n\n    winston.debug('REQUEST ROUTE no_populate:' + req.query.no_populate);\n\n    if (req.query.no_populate != \"true\" && req.query.no_populate != true) {        \n      winston.verbose('REQUEST ROUTE - no_polutate false ', req.headers);\n      q1.populate('department').\n      populate('participatingBots').            //nico già nn gli usa\n      populate('participatingAgents').          //nico già nn gli usa\n      populate('lead').\n      populate({path:'requester',populate:{path:'id_user'}});        //toglilo perche nico lo prende già da snapshot\n    }\n        \n    // cache(cacheUtil.defaultTTL, \"requests-\"+projectId).    \n\n\n    // if (req.query.select_snapshot) {\n    //   winston.info('select_snapshot');\n    //   q1.select(\"+snapshot\");\n    //   // q1.select({ \"snapshot\": 1});\n    // }\n\n    if (req.query.full_text) {     \n      winston.debug('fulltext sort'); \n      q1.sort( { score: { $meta: \"textScore\" } } ) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n    } else {\n      q1.sort(sortQuery);\n    }\n\n    // winston.info('q1',q1);\n\n\n    q1.exec();\n\n    // TODO if ?onlycount=true do not perform find query but only \n    // set q1 to undefined; to skip query\n\n  var q2 =  Request.countDocuments(query).exec();\n\n  var promises = [\n    q1,\n    q2\n  ];\n\n  Promise.all(promises).then(function(results) {\n    var objectToReturn = {\n      perPage: limit,\n      count: results[1],\n      requests: results[0]\n    };\n    winston.debug('REQUEST ROUTE - objectToReturn ', objectToReturn);\n    return res.json(objectToReturn);\n\n  }).catch(function(err){\n    winston.error('REQUEST ROUTE - REQUEST FIND ERR ', err);\n    return res.status(500).send({ success: false, msg: 'Error getting requests.', err: err });\n  });\n  \n\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/users-util.js",
    "content": "var express = require('express');\nvar router = express.Router();\n\nvar User = require(\"../models/user\");\nvar winston = require('../config/winston');\nvar mongoose = require('mongoose');\n\n\n\n\n\n// sponz: realizza mini servizio senza sec\nrouter.get('/:userid', function (req, res) {\n  winston.debug(\"users\");\n  var userid = req.params.userid;\n\n  var isObjectId = mongoose.Types.ObjectId.isValid(userid);\n  winston.debug(\"isObjectId:\"+ isObjectId);\n\n  if (!isObjectId) {\n    return res.status(404).send({ success: false, msg: 'User id not found' });\n  }\n\n  User.findById(userid, 'firstname lastname _id public_website public_email description', function (err, user) {\n    if (err) {\n      winston.error('Error getting object.',err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!user) {\n      winston.warn(\"Object not found with id \" +userid);\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug(\"GET USER BY ID RES JSON\", user);\n    res.json(user);\n  });\n});\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/users.js",
    "content": "var express = require('express');\nvar router = express.Router();\n\nvar User = require(\"../models/user\");\nvar emailService = require(\"../services/emailService\");\nvar winston = require('../config/winston');\nconst authEvent = require('../event/authEvent');\nconst uuidv4 = require('uuid/v4');\nvar uniqid = require('uniqid');\n\nrouter.put('/', function (req, res) {\n\n  winston.debug('UPDATE USER - REQ BODY ', req.body);\n\n  var update = {};\n\n  // update.firstname = req.body.firstname;\n  // update.lastname = req.body.lastname;\n  // update.attributes = req.body.attributes;\n  // update.description = req.body.description;\n\n  if (req.body.firstname != undefined) {\n    update.firstname = req.body.firstname;\n  }\n  if (req.body.lastname != undefined) {\n    update.lastname = req.body.lastname;\n  }\n  if (req.body.attributes != undefined) {\n    update.attributes = req.body.attributes;\n  }\n  if (req.body.description != undefined) {\n    update.description = req.body.description;\n  }\n  if (req.body.public_email != undefined) {\n    update.public_email = req.body.public_email;\n  }\n  if (req.body.public_website != undefined) {\n    update.public_website = req.body.public_website;\n  }\n  if (req.body.phone != undefined) {\n    update.phone = req.body.phone;\n  }\n \n  User.findByIdAndUpdate(req.user.id, update, { new: true, upsert: true }, function (err, updatedUser) {\n    if (err) {\n      winston.error(\"Error putting user\",err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n\n    winston.debug('UPDATED USER ', updatedUser);\n    if (!updatedUser) {\n      return res.status(404).send({ success: false, msg: 'User not found' });\n    }\n\n    authEvent.emit(\"user.update\", {updatedUser: updatedUser, req: req});     \n\n    res.json({ success: true, updatedUser });\n  });\n});\n\n\nrouter.delete('/', function (req, res) {\n\n  // cambia active 0\n  // anonimizzo email \n  // cancello virtualmente progetti owner\n  winston.debug('delete USER - REQ BODY ', req.body);\n\n  \n  var update = {status:0, email: uuidv4()+'@tiledesk.com',firstname: 'anonymized',lastname: 'anonymized'};\n  User.findByIdAndUpdate(req.user.id, update, { new: true, upsert: true }, function (err, updatedUser) {\n    if (err) {\n      winston.error(err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n\n    winston.debug('UPDATED USER ', updatedUser);\n    if (!updatedUser) {\n      return res.status(404).send({ success: false, msg: 'User not found' });\n    }\n\n    authEvent.emit(\"user.delete\", {user: updatedUser, req: req}); \n\n    res.json({ success: true, updatedUser });\n  });\n});\n\n\nrouter.delete('/physical', function (req, res) {\n\n  // cambia active 0\n  // anonimizzo email \n  // cancello virtualmente progetti owner\n  winston.debug('delete USER - REQ BODY ', req.body);\n\n  // TODO use findByIdAndRemove otherwise user don't contains label object\n  User.remove({ _id: req.user.id }, function (err, user) {\n    if (err) {\n      winston.error(err);\n      return res.status(500).send({ success: false, msg: err });\n    }\n\n    winston.debug('deleted USER ', user);  \n    \n    authEvent.emit(\"user.delete\", {user: user, req: req}); \n\n    res.json({ success: true, user });\n  });\n});\n\n\nrouter.put('/changepsw', function (req, res) {\n\n  winston.debug('CHANGE PSW - USER ID: ', req.user.id);\n\n  User.findOne({ _id: req.user.id })\n  .select(\"+password\")\n  .exec(function (err, user) {\n    \n    if (err) throw err;\n    winston.error('CHANGE PSW - FINDONE ERROR ', err)\n    if (!user) {\n      winston.debug('CHANGE PSW - FINDONE USER NOT FOUND ', err)\n      res.status(401).send({ success: false, msg: 'User not found.' });\n    } else {\n      winston.debug('CHANGE PSW - FOUND USER ', user)\n      // check if password matches\n\n      if (req.body.oldpsw) {\n        winston.debug('CHANGE PSW - OLD PSW: ', req.body.oldpsw);\n\n        user.comparePassword(req.body.oldpsw, function (err, isMatch) {\n          if (isMatch && !err) {\n            // if user is found and old password is right\n            winston.debug('* THE PSW MATCH CURRENT PSW * PROCEED WITH THE UPDATE')\n            winston.debug('CHANGE PSW - NEW PSW: ', req.body.newpsw);\n\n            if (req.body.newpsw === req.body.oldpsw) {\n              winston.warn(\"New password can't match the old one\");\n              return res.status(403).send({ success: false, message: \"The new password must be different from the previous one.\"})\n            }\n\n            // const regex = new RegExp(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*])[A-Za-z\\d!@#$%^&*]{8,}$/);\n            // if (!regex.test(req.body.newpsw)) {\n            //   return res.status(403).send({ success: false, message: \"The password does not meet the minimum vulnerability requirements\"})\n            // }\n            \n            user.password = req.body.newpsw\n\n            user.save(function (err, saveUser) {\n\n              if (err) {\n                winston.error('--- > USER SAVE -ERROR ', err)\n                return res.status(500).send({ success: false, msg: 'Error saving object.' });\n              }\n              winston.debug('--- > USER SAVED  ', saveUser)\n              res.status(200).json({ message: 'Password change successful' });\n\n            });\n\n          } else {\n            winston.debug('THE PSW DOES NOT MATCH CURRENT PSW ')\n            res.status(401).send({ success: false, msg: 'Current password is invalid.' });\n          }\n        });\n\n      }\n    }\n  });\n});\n\nrouter.get('/resendverifyemail', function (req, res) {\n  winston.debug('RE-SEND VERIFY EMAIL - LOGGED USER ', req.user);\n  console.log(\"resendverifyemail req.user\", req.user)\n  let user = req.user;\n  try {\n    // TODO req.user.email is null for bot visitor\n    let verify_email_code = uniqid();\n    let redis_client = req.app.get('redis_client');\n    let key = \"emailverify:verify-\" + verify_email_code;\n    let obj = { _id: user._id, email: user.email}\n    let value = JSON.stringify(obj);\n    redis_client.set(key, value, { EX: 900} ) \n    emailService.sendVerifyEmailAddress(user.email, user, verify_email_code);\n    res.status(200).json({ success: true, message: 'Verify email successfully sent' });\n  } catch (e) {\n    winston.debug(\"RE-SEND VERIFY EMAIL error\", e);\n    res.status(500).json({ success: false, message: e });\n  }\n});\n\nrouter.get('/', function (req, res) {\n  winston.debug(\"users\");\n  var userid = req.user.id;\n\n  User.findById(userid, 'email firstname lastname _id emailverified', function (err, user) {\n    if (err) {\n      winston.error('Error getting object.',err);\n      return res.status(500).send({ success: false, msg: 'Error getting object.' });\n    }\n    if (!user) {\n      winston.warn(\"Object not found with id \" +req.user.id);\n      return res.status(404).send({ success: false, msg: 'Object not found.' });\n    }\n    winston.debug(\"GET USER BY ID RES JSON\", user);\n    res.json(user);\n  });\n});\n\nrouter.post('/loginemail', function (req, res) {\n\n  winston.debug(\"/loginemail... req.body: \", req.body);\n  let user_id = req.user._id;\n  let token = req.headers.authorization;\n\n  let project_id = req.body.id_project;\n  let chatbot_id = req.body.bot_id;\n  let namespace_id = req.body.namespace_id;\n\n  if (!project_id) {\n    return res.status(500).send({ success: false, error: \"missing 'id_project' field\" });\n  }\n\n  if (!chatbot_id && !namespace_id) {\n    return res.status(500).send({ success: false, error: \"missing 'bot_id' or 'namespace_id' field\" });\n  }\n\n  User.findById(user_id, (err, user) => {\n    if (err) {\n      return res.status(404).send({ success: false, message: \"No user found\" });\n    }\n    winston.debug(\"user found: \", user);\n\n    emailService.sendEmailRedirectOnDesktop(user.email, token, project_id, chatbot_id, namespace_id)\n    return res.status(200).send({ success: true, message: \"Sending email...\"})\n  })\n\n\n})\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/webhook.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar { KB, Namespace } = require('../models/kb_setting');\nvar winston = require('../config/winston');\nconst JobManager = require('../utils/jobs-worker-queue-manager/JobManagerV2');\nconst { AiReindexService } = require('../services/aiReindexService');\nconst { Webhook } = require('../models/webhook');\nconst webhookService = require('../services/webhookService');\nconst errorCodes = require('../errorCodes');\nconst aiManager = require('../services/aiManager');\nvar ObjectId = require('mongoose').Types.ObjectId;\nconst default_embedding = require('../config/kb/embedding');\n\nconst port = process.env.PORT || '3000';\nlet TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/\";\nif (process.env.TILEBOT_ENDPOINT) {\n    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/\"\n}\nwinston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\nconst KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';\nconst AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL;\nconst JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';\n\nlet jobManager = new JobManager(AMQP_MANAGER_URL, {\n  debug: false,\n  topic: JOB_TOPIC_EXCHANGE,\n  exchange: JOB_TOPIC_EXCHANGE\n})\n\njobManager.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"KbRoute - ConnectPublisher done with status: \", status);\n  }\n})\n\nlet default_engine = {\n  name: \"pinecone\",\n  type: process.env.PINECONE_TYPE || \"pod\",\n  apikey: \"\",\n  vector_size: 1536,\n  index_name: process.env.PINECONE_INDEX\n}\n\nlet default_engine_hybrid = {\n  name: \"pinecone\",\n  type: process.env.PINECONE_TYPE_HYBRID || \"serverless\",\n  apikey: \"\",\n  vector_size: 1536,\n  index_name: process.env.PINECONE_INDEX_HYBRID\n}\n\nrouter.post('/kb/reindex', async (req, res) => {\n\n  winston.verbose(\"/kb/reindex webhook called\")\n  winston.debug(\"(webhook) req.body: \", req.body);\n\n  if (!req.headers['x-auth-token']) {\n    winston.error(\"(webhook) Unauthorized: A x-auth-token must be provided\")\n    return res.status(401).send({ success: false, error: \"Unauthorized\", message: \"A x-auth-token must be provided\" })\n  }\n\n  if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) {\n    winston.error(\"(webhook) Unauthorized: You don't have the authorization to accomplish this operation\")\n    return res.status(401).send({ success: false, error: \"Unauthorized\", message: \"You don't have the authorization to accomplish this operation\" });\n  }\n  \n  let content_id = req.body.content_id;\n\n  let kb;\n  try {\n    kb = await KB.findById(content_id);\n  } catch (err) {\n    winston.error(\"(webhook) Error getting kb content: \", err);\n    return res.status(500).send({ success: false, error: \"Error getting content with id \" + content_id });\n  }\n\n  if (!kb) {\n    winston.warn(\"(webhook) Kb content not found with id \" + content_id + \". Deleting scheduler...\");\n\n    // Assuming the content has been deleted. The scheduler should be stopped and deleted.\n    res.status(200).send({ success: true, message: \"Content no longer exists. Deleting scheduler...\" })\n\n    setTimeout( async () => {\n      let aiReindexService = new AiReindexService();\n      let deleteResponse = await aiReindexService.delete(content_id).catch((err) => {\n        winston.error(\"(webhook) Error deleting scheduler \", err);\n        winston.error(\"(webhook) Error deleting scheduler \" + err);\n        return;\n      });\n      winston.verbose(\"(webhook) delete response: \", deleteResponse);\n      return;\n    }, 10000);\n    return;\n  }\n\n  const namespace_id = kb.namespace;\n  let namespace;\n  try {\n    namespace = await aiManager.checkNamespace(kb.id_project, namespace_id);\n  } catch (err) {\n    let errorCode = err?.errorCode ?? 500;\n    return res.status(errorCode).send({ success: false, error: err.error });\n  }\n\n  if (kb.type === 'sitemap') {\n\n    const urls = await aiManager.fetchSitemap(kb.source).catch((err) => {\n      winston.error(\"(webhook) Error fetching sitemap: \", err);\n      return res.status(500).send({ success: false, error: err });\n    })\n\n    if (urls.length === 0) {\n      return res.status(400).send({ success: false, error: \"No url found on sitemap\" });\n    }\n\n    let existingKbs;\n    try {\n      existingKbs = await KB.find({ id_project: kb.id_project, namespace: namespace_id, sitemap_origin_id: content_id}).lean().exec();\n    } catch(err) {\n      winston.error(\"(webhook) Error finding existing contents: \", err);\n      return res.status(500).send({ success: false, error: \"Error finding existing sitemap contents\" });\n    }\n\n    const result = await aiManager.foundSitemapChanges(existingKbs, urls).catch((err) => {\n      winston.error(\"(webhook) error finding sitemap differecens \", err);\n      return res.status(400).send({ success: false, error: \"Error finding sitemap differecens\" });\n    })\n\n    if (!result) return; // esco qui\n\n    const { addedUrls, removedIds } = result;\n\n    if (removedIds.length > 0) {\n      const idsSet = new Set(removedIds);\n      const kbsToDelete = existingKbs.filter(obj => idsSet.has(obj._id));\n\n      aiManager.removeMultipleContents(namespace, kbsToDelete).catch((err) => {\n        winston.error(\"(webhook) error deleting multiple contents: \", err);\n      })\n    }\n\n    if (addedUrls.length > 0) {\n      const options = {\n        sitemap_origin_id: kb.id,\n        sitemap_origin: kb.source,\n        scrape_type: kb.scrape_type,\n        scrape_options: kb.scrape_options,\n        refresh_rate: kb.refresh_rate\n      }\n      aiManager.addMultipleUrls(namespace, addedUrls, options).catch((err) => {\n        winston.error(\"(webhook) error adding multiple urls contents: \", err);\n      })\n    }\n\n    res.status(200).send({ success: true, message: \"Content queued for reindexing\" });\n\n  } else {\n\n    let json = {\n      id: kb._id,\n      type: kb.type,\n      source: kb.source,\n      content: \"\",\n      namespace: kb.namespace,\n      refresh_rate: kb.refresh_rate,\n      last_refresh: kb.last_refresh\n    }\n  \n    if (kb.scrape_type) {\n      json.scrape_type = kb.scrape_type\n    }\n  \n    if (kb.scrape_options) {\n      json.parameters_scrape_type_4 = {\n        tags_to_extract: kb.scrape_options.tags_to_extract,\n        unwanted_tags: kb.scrape_options.unwanted_tags,\n        unwanted_classnames: kb.scrape_options.unwanted_classnames\n      }\n    }\n  \n    let namespace = await Namespace.findOne({ id: kb.namespace }).catch((err) => {\n      winston.error(\"(webhook) Error getting namespace \", err)\n      return res.status(500).send({ success: false, error: err })\n    })\n  \n    if (!namespace) {\n      winston.warn(\"(webhook) Namespace not found with id \" + kb.namespace);\n      return res.status(500).send({ success: false, error: err })\n    }\n  \n    json.engine = namespace.engine || (namespace.hybrid ? default_engine_hybrid : default_engine);\n    json.hybrid = namespace.hybrid;\n    \n    let embedding = namespace.embedding || default_embedding;\n    embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n    json.embedding = embedding;\n\n    const situated_context = aiManager.normalizeSituatedContext();\n    if (situated_context) {\n      json.situated_context = situated_context;\n    }\n\n    let resources = [];\n    resources.push(json);\n  \n    if (process.env.NODE_ENV !== 'test') {\n      aiManager.scheduleScrape(resources, namespace.hybrid);\n    }\n  \n    res.status(200).send({ success: true, message: \"Content queued for reindexing\" });\n\n  }\n})\n\n\nrouter.post('/kb/status', async (req, res) => {\n\n  winston.info(\"(webhook) kb status called\");\n  winston.info(\"(webhook) req.body: \", req.body);\n\n  winston.info(\"(webhook) x-auth-token: \" + req.headers['x-auth-token']);\n  winston.info(\"(webhook) KB_WEBHOOK_TOKEN: \" + KB_WEBHOOK_TOKEN);\n\n  if (!req.headers['x-auth-token']) {\n    winston.error(\"Unauthorized: A x-auth-token must be provided\")\n    return res.status(401).send({ success: false, error: \"Unauthorized\", message: \"A x-auth-token must be provided\" })\n  }\n\n  if (req.headers['x-auth-token'] != KB_WEBHOOK_TOKEN) {\n    winston.error(\"Unauthorized: You don't have the authorization to accomplish this operation\")\n    return res.status(401).send({ success: false, error: \"Unauthorized\", message: \"You don't have the authorization to accomplish this operation\" });\n  }\n\n  let body = req.body;\n  winston.verbose('/webhook kb status body: ', body);\n\n  let kb_id = body.id;\n  winston.verbose('/webhook kb status body: ' + kb_id);\n\n  let update = {};\n\n  if (body.status) {\n    update.status = body.status;\n  }\n\n  KB.findByIdAndUpdate(kb_id, update, { new: true }, (err, kb) => {\n    if (err) {\n      winston.error(err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    if (!kb) {\n      winston.info(\"Knwoledge Base content to be updated not found\")\n      return res.status(404).send({ success: false, messages: \"Knwoledge Base content not found with id \" + kb_id });\n    }\n\n    winston.info(\"kb updated succesfully: \", kb);\n    res.status(200).send(kb);\n  })\n\n})\n\nrouter.all('/:webhook_id', async (req, res) => {\n\n  let webhook_id = req.params.webhook_id;\n  let payload = req.body;\n  payload.webhook_http_method = req.method;\n  let params = req.query;\n  let dev = params.dev;\n  delete params.dev;\n  if (params) {\n    payload.webhook_query_params = params;\n  }\n\n  let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => {\n    winston.error(\"Error finding webhook: \", err);\n    return res.status(500).send({ success: false, error: err });\n  })\n\n  if (!webhook) {\n    winston.warn(\"Webhook not found with id \" + webhook_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found with id \" + webhook_id });\n  }\n\n  if (!webhook.enabled) {\n    winston.verbose(\"Webhook \" + webhook_id + \" is currently turned off\")\n    return res.status(422).send({ success: false, error: \"Webhook \" + webhook_id + \" is currently turned off\"})\n  }\n\n  const rate_manager = req.app.get(\"rate_manager\");\n  const allowed = await rate_manager.canExecute(webhook.id_project, null, 'webhook');\n  if (!allowed) {\n    winston.warn(\"Webhook rate limit exceeded for project \" + webhook.id_project)\n    return res.status(429).send({ message: \"Rate limit exceeded\"});\n  }\n\n  payload.request_id = \"automation-request-\" + webhook.id_project + \"-\" + new ObjectId() + \"-\" + webhook_id;\n\n  // To delete - Start\n  // This endpoint will be used only for production webhooks, so is no longer\n  // necessary to pass dev and redis_client to webhookService.run().\n  let redis_client = req.app.get('redis_client');\n  // and substitute currect run with the following one\n  //webhookService.run(webhook, payload)\n  // To delete - End\n  webhookService.run(webhook, payload, dev, redis_client).then((response) => {\n    return res.status(200).send(response);\n  }).catch((err) => {\n    if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) {\n      return res.status(422).send({ success: false, message: \"Development webhook is currently turned off\", code: err.code })\n    } else {\n      let status = err.status || 500;\n      return res.status(status).send(err.data);\n    }\n  })\n  \n})\n\nrouter.all('/:webhook_id/dev', async (req, res) => {\n\n  let webhook_id = req.params.webhook_id;\n  let payload = req.body;\n  payload.webhook_http_method = req.method;\n  let params = req.query;\n  delete params.dev;\n  if (params) {\n    payload.webhook_query_params = params;\n  }\n\n  let webhook = await Webhook.findOne({ webhook_id: webhook_id }).catch((err) => {\n    winston.error(\"Error finding webhook: \", err);\n    return res.status(500).send({ success: false, error: err });\n  })\n\n  if (!webhook) {\n    winston.warn(\"Webhook not found with id \" + webhook_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found with id \" + webhook_id });\n  }\n\n  let redis_client = req.app.get('redis_client');\n  webhookService.run(webhook, payload, true, redis_client).then((response) => {\n    return res.status(200).send(response);\n  }).catch((err) => {\n    if (err.code === errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST) {\n      return res.status(422).send({ success: false, message: \"Development webhook is currently turned off\", code: err.code })\n    } else {\n      let status = err.status || 500;\n      return res.status(status).send(err.data);\n    }\n  })\n  \n})\n\n\n// async function generateChatbotToken(chatbot) {\n//   let signOptions = {\n//     issuer: 'https://tiledesk.com',\n//     subject: 'bot',\n//     audience: 'https://tiledesk.com/bots/' + chatbot._id,\n//     jwtid: uuidv4()\n//   };\n\n//   let botPayload = chatbot.toObject();\n//   let botSecret = botPayload.secret;\n\n//   var bot_token = jwt.sign(botPayload, botSecret, signOptions);\n//   return bot_token;\n// }\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/webhooks.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar winston = require('../config/winston');\nconst { Webhook } = require('../models/webhook');\nconst httpUtil = require('../utils/httpUtil');\nconst { customAlphabet } = require('nanoid');\nconst nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz0123456789', 32);\nvar ObjectId = require('mongoose').Types.ObjectId;\n// const port = process.env.PORT || '3000';\n// let TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/ext/\";;\n// if (process.env.TILEBOT_ENDPOINT) {\n//     TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/ext/\"\n// }\n// winston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\nrouter.get('/', async (req, res) => {\n  \n  let id_project = req.projectid;\n\n  let webhooks = await Webhook.find({ id_project: id_project }).catch((err) => {\n    winston.error(\"Error finding webhooks: \", err);\n    return res.status(500).send({ success: false, error: \"Error findin webhooks with for project \" + id_project });\n  })\n\n  res.status(200).send(webhooks);\n\n})\n\nrouter.get('/:chatbot_id', async (req, res) => {\n  \n  let id_project = req.projectid;\n  let chatbot_id = req.params.chatbot_id;\n\n  let webhook = await Webhook.findOne({ id_project: id_project, chatbot_id: chatbot_id }).catch((err) => {\n    winston.error(\"Error finding webhook: \", err);\n    return res.status(500).send({ success: false, error: \"Error findin webhook with for chatbot \" + chatbot_id });\n  })\n\n  if (!webhook) {\n    winston.verbose(\"Webhook not found for chatbot \" + chatbot_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found for chatbot \" + chatbot_id });\n  }\n\n  res.status(200).send(webhook);\n\n})\n\nrouter.get('/id/:webhook_id', async (req, res) => {\n\n  let id_project = req.projectid;\n  let webhook_id = req.params.webhook_id;\n\n  let webhook = await Webhook.findOne({ id_project: id_project, webhook_id: webhook_id }).catch((err) => {\n    winston.error(\"Error finding webhook: \", err);\n    return res.status(500).send({ success: false, error: \"Error findin webhook with id \" + webhook_id });\n  })\n\n  if (!webhook) {\n    winston.verbose(\"Webhook not found with id \" + webhook_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found with id \" + webhook_id });\n  }\n\n  res.status(200).send(webhook);\n})\n\n\nrouter.post('/', async (req, res) => {\n\n  let id_project = req.projectid;\n\n  let webhook = new Webhook({\n    id_project: id_project,\n    name: \"webhook-\" + this.webhook_id,\n    chatbot_id: req.body.chatbot_id || req.body.id_faq_kb,\n    block_id: req.body.block_id,\n    copilot: req.body.copilot,\n    async: req.body.async\n  })\n\n  webhook.save((err, savedWebhook) => {\n    if (err) {\n      if (err.code === 11000) {\n        winston.verbose(\"Webhook already exists for chatbot \" + webhook.chatbot_id);\n        return res.status(403).send({ success: false, message: \"Webhook already exists for chatbot \" + webhook.chatbot_id });\n      } \n      winston.error(\"Error saving new webhook \", err);\n      return res.status(500).send({ success: false, error: err });\n    }\n\n    res.status(200).send(savedWebhook);\n  })\n\n})\n\nrouter.post('/preload/:webhook_id', async (req, res) => {\n\n  let id_project = req.projectid;\n  let webhook_id = req.params.webhook_id;\n  let request_id = \"automation-request-\" + id_project + \"-\" + new ObjectId() + \"-\" + webhook_id;\n  let redis_client = req.app.get('redis_client');\n\n  try {\n    let key = \"logs:webhook:\" + id_project + \":\" + webhook_id;\n    let value = JSON.stringify({ request_id: request_id });\n    redis_client.set(key, value, { EX: 900 });\n    res.status(200).send({ success: true, message: \"Webhook preloaded successfully\", request_id: request_id });\n  } catch(err) {\n    winston.error(\"Error adding key in cache \", err);\n    res.status(500).send({ success: false, message: \"Unable to start development webhook\" })\n  }\n\n})\n\nrouter.delete('/preload/:webhook_id', async (req, res) => {\n  \n  let id_project = req.projectid;\n  let webhook_id = req.params.webhook_id;\n  let key = \"logs:webhook:\" + id_project + \":\" + webhook_id;\n  let redis_client = req.app.get('redis_client');\n  \n  try {\n    await redis_client.del(key);\n    res.status(200).send({ success: true, message: \"Development webhook stopped\" })\n  } catch(err) {\n    winston.error(\"Error deleting key from cache \", err);\n    res.status(500).send({ success: false, message: \"Unable to stop development webhook\" })\n  }\n\n})\n\n// router.post('/webhook_id', async (req, res) => {\n\n//   let id_project = req.projectid;\n//   let webhook_id = req.params.webhook_id;\n//   let payload = req.body;\n\n//   let webhook = await Webhook.findOne({ id_project: id_project, webhook_id: webhook_id }).catch((err) => {\n//     winston.error(\"Error finding webhook: \", err);\n//     return res.status(500).send({ success: false, error: err });\n//   })\n\n//   if (!webhook) {\n//     winston.warn(\"Webhook not found with id \" + webhook_id);\n//     return res.status(404).send({ success: false, error: \"Webhook not found with id \" + webhook_id });\n//   }\n\n//   let url = TILEBOT_ENDPOINT + 'block/' + id_project + \"/\" + webhook.chatbot_id + \"/\" + webhook.block_id;\n\n//   payload.async = webhook.async;\n  \n//   let response = httpUtil.post(url, payload).catch((err) => {\n//     winston.error(\"Error calling webhook on post: \", err);\n//     return res.status(500).send({ success: false, error: err });\n//   })\n\n//   res.status(200).send(response);\n\n// })\n\nrouter.put(\"/:chatbot_id/regenerate\", async (req, res) => {\n  \n  let id_project = req.projectid;\n  let chatbot_id = req.params.chatbot_id;\n\n  let update = {\n    webhook_id: nanoid()\n  }\n\n  let updatedWebhook = await Webhook.findOneAndUpdate({ id_project: id_project, chatbot_id: chatbot_id }, update, { new: true }).catch((err) => {\n    winston.error(\"Error updating webhook \", err);\n    return res.status(500).send({ success: false, error: \"Error updating webhook for chatbot \" + chatbot_id });\n  })\n\n  if (!updatedWebhook) {\n    winston.verbose(\"Webhook not found for chatbot \" + chatbot_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found for chatbot \" + chatbot_id });\n  }\n\n  res.status(200).send(updatedWebhook);\n})\n\nrouter.put(\"/:chatbot_id\", async (req, res) => {\n\n  let id_project = req.projectid;\n  let chatbot_id = req.params.chatbot_id;\n\n  let update = {};\n\n  if (req.body.hasOwnProperty(\"async\")) {\n    update.async = req.body.async;\n  }\n\n  if (req.body.hasOwnProperty(\"copilot\")) {\n    update.copilot = req.body.copilot;\n  }\n\n  if (req.body.hasOwnProperty('enabled')) {\n    update.enabled = req.body.enabled;\n  }\n\n  let updatedWebhook = await Webhook.findOneAndUpdate({ id_project: id_project, chatbot_id: chatbot_id }, update, { new: true }).catch((err) => {\n    winston.error(\"Error updating webhook \", err);\n    return res.status(500).send({ success: false, error: \"Error updating webhook for chatbot \" + chatbot_id });\n  })\n\n  if (!updatedWebhook) {\n    winston.verbose(\"Webhook not found for chatbot \" + chatbot_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found for chatbot \" + chatbot_id });\n  }\n\n  res.status(200).send(updatedWebhook);\n})\n\nrouter.put('/update/:webhook_id', async (req, res) => {\n  \n  let id_project = req.projectid;\n  let webhook_id = req.params.webhook_id;\n\n  let update = {};\n\n  if (req.body.hasOwnProperty(\"async\")) {\n    update.async = req.body.async;\n  }\n\n  if (req.body.hasOwnProperty(\"copilot\")) {\n    update.copilot = req.body.copilot;\n  }\n\n  if (req.body.hasOwnProperty('enabled')) {\n    update.enabled = req.body.enabled;\n  }\n\n  let updatedWebhook = await Webhook.findOneAndUpdate({ id_project: id_project, webhook_id: webhook_id }, update, { new: true }).catch((err) => {\n    winston.error(\"Error updating webhook \", err);\n    return res.status(500).send({ success: false, error: \"Error updating webhook for chatbot \" + chatbot_id });\n  })\n\n  if (!updatedWebhook) {\n    winston.verbose(\"Webhook not found with id \" + webhook_id);\n    return res.status(404).send({ success: false, error: \"Webhook not found with id \" + webhook_id });\n  }\n\n  res.status(200).send(updatedWebhook);\n\n})\n\nrouter.delete(\"/:chatbot_id\", async (req, res) => {\n\n  let id_project = req.projectid;\n  let chatbot_id = req.params.chatbot_id;\n\n  await Webhook.deleteOne({ id_project: id_project, chatbot_id: chatbot_id }).catch((err) => {\n    winston.error(\"Error deleting webhook \", err);\n    return res.status(500).send({ success: false, error: \"Error deleting webhook for chatbot \" + chatbot_id });\n  })\n\n  res.status(200).send({ success: true, message: \"Webhook for chatbot \" + chatbot_id +  \" deleted successfully\" });\n})\n\nrouter.delete(\"/delete/:webhook_id\", async (req, res) => {\n\n  let id_project = req.projectid;\n  let webhook_id = req.params.webhook_id;\n\n  await Webhook.deleteOne({ id_project: id_project, webhook_id: webhook_id }).catch((err) => {\n    winston.error(\"Error deleting webhook \", err);\n    return res.status(500).send({ success: false, error: \"Error deleting webhook with id \" + webhook_id });\n  })\n\n  res.status(200).send({ success: true, message: \"Webhook \" + webhook_id +  \" deleted successfully\" });\n})\n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/widget.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Project = require(\"../models/project\");\nvar winston = require('../config/winston');\nvar Project_user = require(\"../models/project_user\");\nvar operatingHoursService = require(\"../services/operatingHoursService\");\nvar AnalyticResult = require(\"../models/analyticResult\");\nvar Department = require(\"../models/department\");\nvar RoleConstants = require(\"../models/roleConstants\");\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar pubModulesManager = require('../pubmodules/pubModulesManager');  // on constructor init is undefined beacusae pub module is loaded after\n// console.log(\"pubModulesManager.cache\", pubModulesManager.cache);\nconst Faq_kb = require(\"../models/faq_kb\");\n\nrouter.get('/load', function(req, res, next) {\n\n  winston.debug(req.projectid);\n  \n  // https://stackoverflow.com/questions/24258782/node-express-4-middleware-after-routes\n  next();      // <=== call next for following middleware \n  // redirect to widget\n});\n\nrouter.get('/', async (req, res, next) => {\n\n    winston.debug(req.projectid);\n\n\n    var getIp = function() {\n      return new Promise(function (resolve, reject) {\n        // console.log(\"getIp\")\n      //  var ip = req.ip;\n      var ip = req.headers['x-forwarded-for'] || \n       req.connection.remoteAddress || \n       req.socket.remoteAddress ||\n       (req.connection.socket ? req.connection.socket.remoteAddress : null);\n        winston.debug(\"ip:\"+ ip);\n        return resolve(ip);\n      });\n    };\n\n\n    \n    // console.log(\"pubModulesManager.cache\",pubModulesManager.cache);\n    // console.log(\"cacheClient\",cacheClient);\n    let cacheClient = undefined;\n    if (pubModulesManager.cache) {\n      cacheClient = pubModulesManager.cache._cache._cache;  //_cache._cache to jump directly to redis modules without cacheoose wrapper (don't support await)\n    }\n\n    var cacheKey = req.projectid+\":widgets\";\n    \n    if (cacheEnabler.widgets && cacheClient) {\n      winston.debug(\"Getting cache for widgets key: \" + cacheKey);       \n      const value = await cacheClient.get(cacheKey);\n\n      if (value) {\n        winston.debug(\"Getted cache for widgets key: \" + cacheKey + \" value:\", value);   \n\n        value.ip = await getIp(); //calculate ip each time without getting it from cache \n        \n        winston.debug(\"value\",value);\n\n        res.json(value);\n        // https://stackoverflow.com/questions/24258782/node-express-4-middleware-after-routes\n        next();      // <=== call next for following middleware \n        return;\n      } else {\n          winston.debug(\"Widget cache NOT found\");\n      }\n      \n    }\n    // console.log(\"/widgets without cache found\");\n\n    var availableUsers = function() {\n      winston.debug('availableUsers:');\n      return new Promise(function (resolve, reject) {\n        if (!req.project) {\n          return reject({err: \"Project Not Found\"});\n        }\n      operatingHoursService.projectIsOpenNow(req.projectid, function (isOpen, err) {    \n          winston.debug('isOpen:'+ isOpen);\n          if (isOpen) {            \n            // rolequery\n            Project_user.find({ id_project: req.projectid, user_available: true, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" }).\n            // Project_user.find({ id_project: req.projectid, user_available: true, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" }).\n              populate('id_user').\n              exec(function (err, project_users) {\n                winston.debug('project_users:'+ project_users);\n                if (project_users) {    \n                  user_available_array = [];\n                  project_users.forEach(project_user => {\n                    if (project_user.id_user) {\n                      user_available_array.push({ \"id\": project_user.id_user._id, \"firstname\": project_user.id_user.firstname });\n                    } else {\n                      winston.debug('else:');\n                    }\n                  });      \n                  winston.debug('user_available_array:'+ JSON.stringify(user_available_array));          \n                  return resolve(user_available_array);\n                }\n              });\n          } else {          \n            return resolve([]);\n          }\n        });\n      });\n  };\n\n  var waiting = function() {\n    return new Promise(function (resolve, reject) {\n      AnalyticResult.aggregate([\n        //last 4\n        { $match: {\"id_project\":req.projectid, \"createdAt\" : { $gte : new Date((new Date().getTime() - (4 * 60 * 60 * 1000))) }} },\n        { \"$group\": { \n          \"_id\": \"$id_project\", \n          \"waiting_time_avg\":{\"$avg\": \"$waiting_time\"}\n        }\n      },\n      \n    ])\n    // .cache(cacheUtil.longTTL, req.projectid+\":analytics:query:waiting:avg:4hours\")        \n    .exec(function(err, result) {\n          return resolve(result);\n    });\n  });\n  };\n\n  \n\n  var botsRules = function() {\n    return new Promise(function (resolve, reject) {\n      Faq_kb.find({ \"id_project\": req.projectid, \"trashed\": { $in: [null, false] } }, function (err, bots) {\n        winston.debug(\"bots\",bots);\n        let rules = [];\n        bots.forEach(function(bot) { \n          winston.debug(\"bot.attributes\",bot.attributes);\n          // && bot.attributes.rules.length > 0\n          if (bot.attributes && bot.attributes.rules) {\n            winston.debug(\"bot.attributes.rules\",bot.attributes.rules);\n            bot.attributes.rules.forEach(function(rule) {\n              rules.push(rule);\n            });\n            // rules.concat(bot.attributes.rules);\n          }\n        });\n        winston.debug(\"resolve\",rules);\n        // return resolve(bots);\n        return resolve(rules);\n        \n      });\n    });\n  }\n\n  \n  var getDepartments = function(req) {\n\n    return new Promise(function (resolve, reject) {\n\n      var query = { \"id_project\": req.projectid, \"status\": 1 };\n\n      winston.debug(\"req.project:\" + JSON.stringify(req.project));\n      \n      if (req.project) {\n                                    //secondo me qui manca un parentesi tonda per gli or\n        if (req.project.profile && (req.project.profile.type === 'free' && req.project.trialExpired === true) || (req.project.profile.type === 'payment' && req.project.isActiveSubscription === false)) {\n          query.default = true;\n        }\n  \n        winston.debug(\"query:\", query);\n  \n        Department.find(query).exec(function(err, result) {\n              return resolve(result);\n        });\n      } else {        \n          return reject({err: \"Project Not Found\"});        \n      }\n\n\n    });\n\n  }\n\n\n  var getProject = function(req) {\n    winston.debug('getProject.');\n\n    return new Promise(function (resolve, reject) {\n\n       //@DISABLED_CACHE .cache(cacheUtil.queryTTL, \"projects:query:id:status:100:\"+req.projectid+\":select:-settings\")            \n      \n      Project.findOne({_id: req.projectid, status: 100}).select('-ipFilter -ipFilterEnabled').exec(function(err, project) {\n        // not use .lean I need project.trialExpired \n\n          if (err) {\n            return reject({err: \"Project Not Found\"});        \n          }\n\n\n          winston.debug(\"project\", project);\n\n          // This code filters the project settings to only include the properties\n          // 'allowed_urls', 'allowed_urls_list', 'allowed_send_emoji' and 'allowed_upload_extentions' removing all others.\n          // Removed the \"-settings\" command from the query that excluded the entire \"settings\" field from the selection\n          if (project && project.settings) {\n            const { allowed_urls, allowed_urls_list, allow_send_emoji, allowed_upload_extentions } = project.settings;\n            project.settings = {};\n            Object.assign(project.settings, \n              allowed_urls !== undefined ? { allowed_urls } : {},\n              allowed_urls_list !== undefined ? { allowed_urls_list } : {},\n              allow_send_emoji !== undefined ? { allow_send_emoji } : {},\n              allowed_upload_extentions !== undefined ? { allowed_upload_extentions } : {}\n            );\n          }\n\n          // ProjectSetter project not found with id: 62d8cf8b2b10b30013bb9b99\n          // Informazioni\n          // 2022-07-27 14:32:14.772 CESTerror: Error getting widget. {\"err\":\"Project Not Found\"}\n          // Informazioni\n          // 2022-07-27 14:32:14.778 CESTerror: uncaughtException: Cannot read property 'profile' of null\n          // Informazioni\n          // 2022-07-27 14:32:14.778 CESTTypeError: Cannot read property 'profile' of null at /usr/src/app/routes/widget.js:132:124 at /usr/src/app/node_modules/mongoose/lib/model.js:5074:18 at processTicksAndRejections (internal/process/task_queues.js:79:11) {\"date\":\"Wed Jul 27 2022 12:32:14 GMT+0000 (Coordinated Universal Time)\",\"error\":{},\"exception\":true,\"os\":{\"loadavg\":[0.26,0.51,0.58],\"uptime\":1028128},\"process\":{\"argv\":[\"/usr/local/bin/node\",\"/usr/src/app/bin/www\"],\"cwd\":\"/usr/src/app\",\"execPath\":\"/usr/local/bin/node\",\"gid\":0,\"memoryUsage\":{\"arrayBuffers\":128833077,\"external\":130521753,\"heapTotal\":110641152,\"heapUsed\":85605912,\"rss\":310054912},\"pid\":26,\"uid\":0,\"version\":\"v12.22.12\"},\"stack\":\"TypeError: Cannot read property 'profile' of null\\n at /usr/src/app/routes/widget.js:132:124\\n at /usr/src/app/node_modules/mongoose/lib/model.js:5074:18\\n at processTicksAndRejections (internal/process/task_queues.js:79:11)\",\"trace\":[{\"column\":124,\"file\":\"/usr/src/app/routes/widget.js\",\"function\":null,\"line\":132,\"method\":null,\"native\":false},{\"column\":18,\"file\":\"/usr/src/app/node_modules/mongoose/lib/model.js\",\"function\":null,\"line\":5074,\"method\":null,\"native\":false},{\"column\":11,\"file\":\"internal/process/task_queues.js\",\"function\":\"processTicksAndRejections\",\"line\":79,\"method\":null,\"native\":false}]}\n\n          // console.log(\"project!=null\",project!=null);\n          // console.log(\"project.profile\",project.profile);\n                                              //secondo me qui manca un parentesi tonda per gli or\n          if (project  && project.profile && ((project.profile.type === 'free' && project.trialExpired === true) || (project.profile.type === 'payment' && project.isActiveSubscription === false))) {\n            winston.debug('getProject remove poweredBy tag', project);\n\n            if (project.widget) {\n              project.widget.poweredBy = undefined;\n              project.widget.baloonImage = undefined;            \n            }\n            \n          }\n\n        return resolve(project);\n      });        \n\n    });\n\n  }\n\n// TOOD add labels\n    Promise.all([\n\n        getProject(req)\n      ,\n        availableUsers()\n      ,\n        getDepartments(req)\n      // ,\n        // waiting()unused used 620e87f02e7fda00350ea5a5/publicanalytics/waiting/current\n      , \n        getIp()\n      ,\n        botsRules()\n     \n\n    ]).then(function(all) {\n      // console.log(\"all\", all);\n      let result = {project: all[0], user_available: all[1], departments: all[2], ip: all[3], botsRules: all[4]};\n      // let result = {project: all[0], user_available: all[1], departments: all[2], waiting: all[3], ip: all[4]};\n\n      \n      if (cacheEnabler.widgets && cacheClient) {   \n        let cloned_result = Object.assign({}, result);     \n        delete cloned_result.ip; //removing uncachable ip from cache\n        winston.debug(\"Creating cache for widgets key: \" + cacheKey);       \n        cacheClient.set(cacheKey, cloned_result, cacheUtil.longTTL, (err, reply) => {\n            winston.verbose(\"Created cache for widgets\",{err:err});\n            winston.debug(\"Created cache for widgets reply:\"+reply);\n        });\n\n      }\n      \n      res.json(result);\n      // https://stackoverflow.com/questions/24258782/node-express-4-middleware-after-routes\n      next();      // <=== call next for following middleware \n\n    }).catch(function(err) {\n      winston.error('Error getting widget.', err);\n      return res.status(500).send({success: false, msg: 'Error getting widget.'});\n    });\n\n\n  });\n\n\n\n  \n\n\n  router.get('/ip', function(req, res, next) {\n  \n     var xforwarded = req.headers['x-forwarded-for'];\n     winston.info('xforwarded'+ xforwarded);\n\n     var connectionRemoteAddress  = req.connection.remoteAddress;\n     winston.info('connectionRemoteAddress'+ connectionRemoteAddress);\n\n     var socketRemoteAddress = req.socket.remoteAddress;\n     winston.info('socketRemoteAddress'+ socketRemoteAddress);\n\n    if (req.connection.socket ) {\n      var connectionSocketRemoteAddress = req.connection.socket.remoteAddress;\n      winston.info('connectionSocketRemoteAddress'+ connectionSocketRemoteAddress);\n\n    }\n    \n\n    var ip = req.headers['x-forwarded-for'] || \n     req.connection.remoteAddress || \n     req.socket.remoteAddress ||\n     (req.connection.socket ? req.connection.socket.remoteAddress : null);\n    winston.info(\"ip:\"+ ip);\n\n\n\n\n    const ipStandard = (req.headers['x-forwarded-for'] || '').split(',').shift().trim() ||        //https://stackoverflow.com/questions/8107856/how-to-determine-a-users-ip-address-in-node\n    req.socket.remoteAddress\n\n    winston.info(\"standard ip: \"+ipStandard); // ip address of the user\n\n\n\n    // const parseIp = (req) =>\n    // req.headers['x-forwarded-for']?.split(',').shift()\n    // || req.socket?.remoteAddress\n\n    \n\n    let parseIp = req.socket.remoteAddress;\n\n    const xFor =  req.headers['x-forwarded-for'];\n    winston.info(\"parseIp xFor: \"+xFor);\n\n    if (xFor ) {\n      const xForArr = xFor.split(',');\n      if (xForArr && xForArr.length>0) {\n        parseIp = xForArr.shift();\n        winston.info(\"parseIp xFor parseIp: \"+parseIp);\n      }\n    }\n    winston.info(\"parseIp: \"+parseIp); // ip address of the user\n\n\n    res.json( {ip:ip, ipStandard:ipStandard, parseIp: parseIp} );\n\n\n  });\n\n  \n\n\nmodule.exports = router;\n"
  },
  {
    "path": "routes/widgetLoader.js",
    "content": "var express = require('express');\nvar router = express.Router();\nvar Project = require(\"../models/project\");\nvar winston = require('../config/winston');\nvar widgetConfig = require('../config/widget');\n\nvar widgetLocation = process.env.WIDGET_LOCATION || widgetConfig.location;\nvar url = require('url');\n\nrouter.get('/load', function(req, res) {\n  var query = url.parse(req.url).query;\n  winston.debug(query);\n  // TODO chech if query is null\n  res.redirect(widgetLocation+'?'+query);\n\n});\n  \n\nrouter.get('/v5/:project_id', function(req, res) {\n\n  var project_id = req.params.project_id;\n  winston.debug(\"project_id: \" + project_id);\n\n  res.type('.js');\n\n  var js = `\n    window.tiledeskSettings= \n    {\n        projectid: \"${project_id}\"\n    };\n    (function(d, s, id) { \n        var w=window; var d=document; var i=function(){i.c(arguments);};\n        i.q=[]; i.c=function(args){i.q.push(args);}; w.Tiledesk=i;                    \n        var js, fjs=d.getElementsByTagName(s)[0];\n        if (d.getElementById(id)) return;\n        js=d.createElement(s); \n        js.id=id; js.async=true; js.src=\"${widgetLocation}launch.js\";\n        fjs.parentNode.insertBefore(js, fjs);\n    }(document,'script','tiledesk-jssdk'));\n  `;\n\n  winston.debug(\"js: \" + js);\n\n  res.send(js);\n\n});\n\n\nvar widgetTestLocation = process.env.WIDGET_TEST_LOCATION || widgetConfig.testLocation;\n\nrouter.get('/test/load', function(req, res) {\n  var query = url.parse(req.url).query;\n  res.redirect(widgetTestLocation+'?'+query);\n});\n\nmodule.exports = router;\n"
  },
  {
    "path": "services/BotSubscriptionNotifier.js",
    "content": "const botEvent = require('../event/botEvent');\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\nconst Faq_kb = require('../models/faq_kb');\nconst uuidv4 = require('uuid/v4');\n\nvar request = require('retry-request', {\n  request: require('request')\n});\n\nvar webhook_origin = process.env.WEBHOOK_ORIGIN || \"http://localhost:3000\";\nwinston.debug(\"webhook_origin: \"+webhook_origin);\n\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\n\n\n\nclass BotSubscriptionNotifier {\n   \n  \n  notify(bot,secret, payload) {\n  \n      winston.debug(\"BotSubscriptionNotifier bot\", bot.toObject());\n      winston.debug(\"BotSubscriptionNotifier payload\", payload );\n\n      var url = bot.url;\n\n      if (bot.type == \"tilebot\" && payload.request && payload.request.attributes && payload.request.attributes.sourcePage  && payload.request.attributes.sourcePage.indexOf(\"&td_draft=true\")>-1) {\n          url = url.substr(0,url.lastIndexOf(\"/\")+1)+bot.id;\n        // url = url + \"?forced_bot_id=\"+bot.id;   \n        winston.verbose(\"BotSubscriptionNotifier it is a tilebot test page. Switch to current chabot id: \"+ url);              \n      }\n\n      // if (url.startsWith(\"$ext_url\")) {\n      //   // url = url.replace (\"$res_bot_url\", prendi da env)\n      // }\n\n      //Removed snapshot from request \n      delete payload.request.snapshot\n      \n      var json = {timestamp: Date.now(), payload: payload};\n    \n\n      json[\"hook\"] = bot;\n\n\n      var signOptions = {\n        issuer:  'https://tiledesk.com',\n        subject:  'bot',\n        audience:  'https://tiledesk.com/bots/'+bot._id,   \n        jwtid: uuidv4()       \n      };\n\n      // TODO metti bot_? a user._id\n\n      // tolgo description, attributes\n      let botPayload = bot.toObject();\n      delete botPayload.secret;\n      delete botPayload.description;\n      delete botPayload.attributes;\n\n      var token = jwt.sign(botPayload, secret, signOptions);\n      json[\"token\"] = token;\n\n\n      winston.debug(\"BotSubscriptionNotifier notify json \", json );\n\n          request({\n            url: url,\n            headers: {\n             'Content-Type' : 'application/json', \n             'User-Agent': 'tiledesk-bot',\n             'Origin': webhook_origin\n              //'x-hook-secret': s.secret\n            },\n            json: json,\n            method: 'POST'\n\n          }, function(err, result, json){            \n            winston.verbose(\"SENT notify for bot with url \" + url +  \" with err \" + err);\n            winston.debug(\"SENT notify for bot with url \", result);\n            if (err) {\n              winston.error(\"Error sending notify for bot with url \" + url + \" with err \" + err);\n              //TODO Reply with error\n              // next(err, json);\n            }\n          });\n    \n}\n\n\n  start() {\n    winston.debug('BotSubscriptionNotifier start');\n    //modify to async\n    botEvent.on('bot.message.received.notify.external', function(botNotification) {\n      var bot = botNotification.bot;\n      var secret = bot.secret;\n      winston.debug('bot.message.received.notify.external: '+secret);\n\n    //   winston.debug('getting botWithSecret');\n    //   let qbot = Faq_kb.findById(bot._id).select('+secret')\n\n    //   if (cacheEnabler.faq_kb) {\n    //     let id_project = bot.id_project;\n    //     winston.debug(\"id_project.id_project:\"+id_project);\n    //     qbot.cache(cacheUtil.defaultTTL, id_project+\":faq_kbs:id:\"+bot._id+\":secret\")\n    //     winston.debug('faq_kb BotSubscriptionNotifier cache enabled');\n    //   }\n\n    //   qbot.exec(function (err, botWithSecret){   //TODO add cache_bot_here????\n    //     if (err) {\n    //       winston.debug('Error getting botWithSecret', err);\n    //     }\n    //     botSubscriptionNotifier.notify(bot, botWithSecret, botNotification.message);\n    //   });\n      \n    botSubscriptionNotifier.notify(bot, secret, botNotification.message);\n\n\n    });\n\n\n\n    winston.info('BotSubscriptionNotifier started');\n\n  }\n\n\n\n};\n\nvar botSubscriptionNotifier = new BotSubscriptionNotifier();\n\n\nmodule.exports = botSubscriptionNotifier;"
  },
  {
    "path": "services/QuoteManager.js",
    "content": "const { ceil, floor } = require('lodash');\nconst moment = require('moment');\nlet winston = require('../config/winston');\nconst requestEvent = require('../event/requestEvent');\nconst messageEvent = require('../event/messageEvent');\nconst emailEvent = require('../event/emailEvent');\n\n// NEW\n// const PLANS_LIST = {\n//     FREE_TRIAL: { requests: 3000,   messages: 0,    tokens: 250000,     email: 200,    chatbots: 20,       kbs: 50 }, // same as PREMIUM\n//     SANDBOX:    { requests: 200,    messages: 0,    tokens: 100000,     email: 200,    chatbots: 2,        kbs: 50 },\n//     BASIC:      { requests: 1000,   messages: 0,    tokens: 2000000,    email: 200,    chatbots: 10,       kbs: 200},\n//     PREMIUM:    { requests: 3000,   messages: 0,    tokens: 5000000,    email: 200,    chatbots: 20,       kbs: 500},\n//     CUSTOM:     { requests: 3000,   messages: 0,    tokens: 5000000,    email: 200,    chatbots: 20,       kbs: 500}\n// }\n\nconst PLANS_LIST = {\n    //FREE_TRIAL: { requests: 200,    messages: 0,    tokens: 100000,     voice_duration: 0,       email: 200,     chatbots: 20,      namespace: 3,   kbs: 50     }, // same as PREMIUM\n    SANDBOX:    { requests: 200,    messages: 0,    tokens: 100000,     voice_duration: 0,          email: 200,     chatbots: 2,    namespace: 1,   kbs: 50 },\n    BASIC:      { requests: 800,    messages: 0,    tokens: 2000000,    voice_duration: 0,          email: 200,     chatbots: 5,    namespace: 1,   kbs: 150 },\n    PREMIUM:    { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 0,          email: 200,     chatbots: 20,   namespace: 3,   kbs: 300 },\n    TEAM:       { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 0,          email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 },\n    //CUSTOM:     { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 120000,  email: 200,     chatbots: 50,      namespace: 10,  kbs: 1000   },\n    // FROM MARCH 2025\n    FREE_TRIAL: { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 120000,     email: 200,     chatbots: 5,    namespace: 1,   kbs: 50 },  // same as PRO\n    STARTER:    { requests: 800,    messages: 0,    tokens: 2000000,    voice_duration: 0,          email: 200,     chatbots: 5,    namespace: 1,   kbs: 150 },\n    PRO:        { requests: 3000,   messages: 0,    tokens: 5000000,    voice_duration: 0,          email: 200,     chatbots: 20,   namespace: 3,   kbs: 300 },\n    BUSINESS:   { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 0,          email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 },\n    CUSTOM:     { requests: 5000,   messages: 0,    tokens: 10000000,   voice_duration: 120000,     email: 200,     chatbots: 50,   namespace: 10,  kbs: 1000 }\n}\n\n\n\nconst typesList = ['requests', 'messages', 'email', 'tokens', 'voice_duration', 'chatbots', 'kbs']\n\nlet quotes_enabled = true;\n\nclass QuoteManager {\n\n    constructor(config) {\n\n        if (!config) {\n            throw new Error('config is mandatory')\n        }\n\n        if (!config.tdCache) {\n            throw new Error('config.tdCache is mandatory')\n        }\n\n        this.tdCache = config.tdCache;\n        this.project;\n\n    }\n\n    // INCREMENT KEY SECTION - START\n    async incrementRequestsCount(project, request) {\n\n        this.project = project;\n        let key = await this.generateKey(request, 'requests');\n        winston.verbose(\"[QuoteManager] incrementRequestsCount key: \" + key);\n\n        await this.tdCache.incr(key)\n        this.sendEmailIfQuotaExceeded(project, request, 'requests', key);\n        return key;\n    }\n\n    async incrementMessagesCount(project, message) {\n\n        this.project = project;\n        let key = await this.generateKey(message, 'messages');\n        winston.verbose(\"[QuoteManager] incrementMessagesCount key: \" + key);\n\n        await this.tdCache.incr(key)\n        return key;\n    }\n\n    async incrementEmailCount(project, email) {\n\n        this.project = project;\n        let key = await this.generateKey(email, 'email');\n        winston.verbose(\"[QuoteManager] incrementEmailCount key: \" + key);\n\n        await this.tdCache.incr(key)\n        this.sendEmailIfQuotaExceeded(project, email, 'email', key);\n        return key;\n    }\n\n    async incrementTokenCount(project, data) { // ?? cosa passo? il messaggio per vedere la data?\n\n        this.project = project;\n        let key = await this.generateKey(data, 'tokens');\n        winston.verbose(\"[QuoteManager] incrementTokenCount key: \" + key);\n\n        if (quotes_enabled === false) {\n            winston.debug(\"QUOTES DISABLED - incrementTokenCount\")\n            return key;\n        }\n\n        let tokens = data.tokens * data.multiplier;\n        await this.tdCache.incrbyfloat(key, tokens);\n        // await this.tdCache.incrby(key, tokens);\n        this.sendEmailIfQuotaExceeded(project, data, 'tokens', key);\n        return key;\n    }\n\n    async incrementVoiceDurationCount(project, request) {\n\n        this.project = project;\n        let key = await this.generateKey(request, 'voice_duration');\n        winston.verbose(\"[QuoteManager] incrementVoiceDurationCount key: \" + key);\n\n        if (quotes_enabled === false) {\n            winston.debug(\"QUOTES DISABLED - incrementVoiceDurationCount\")\n            return key;\n        }\n\n        if (request?.duration) {\n            let duration = Math.round(request.duration / 1000); // from ms to s\n            await this.tdCache.incrby(key, duration);\n\n            this.sendEmailIfQuotaExceeded(project, request, 'voice_duration', key);\n        }\n    }\n    // INCREMENT KEY SECTION - END\n\n\n    async generateKey(object, type) {\n\n        let objectDate = moment(object.createdAt);\n        let subscriptionDate;\n\n        if (this.project.isActiveSubscription === true) {\n            if (this.project.profile.subStart) {\n                subscriptionDate = moment(this.project.profile.subStart);\n                winston.debug(\"Subscription date from subStart: \" + subscriptionDate.toISOString());\n            } else {\n                // it should never happen\n                winston.error(\"Error: quote manager - isActiveSubscription is true but subStart does not exists.\")\n            }\n        } else {\n            if (this.project.profile.subEnd) {\n                subscriptionDate = moment(this.project.profile.subEnd);\n                winston.debug(\"Subscription date from subEnd: \" + subscriptionDate.toISOString());\n            } else {\n                subscriptionDate = moment(this.project.createdAt);\n                winston.debug(\"Subscription date from project createdAt: \" + subscriptionDate.toISOString());\n            }\n        }\n\n        // Calculate the difference in months between the object date and the subscription date\n        let diffInMonths = objectDate.diff(subscriptionDate, 'months');\n        winston.debug(\"diffInMonths: \", diffInMonths)\n\n        // Make a clone of the subscription date --> this operation could be avoided\n        // Get the renewal date adding diffInMonths. Moment.js manage automatically the less longer month. \n        // E.g. if subscription date is 31 jan the renewals will be, 28/29 feb, 31 mar, 30 apr, etc.\n        let renewalDate = subscriptionDate.clone().add(diffInMonths, 'months');\n        // Force the renewal date equal to the last day of the month --> this operation could be avoided\n        if (renewalDate.date() !== subscriptionDate.date()) {\n            renewalDate = renewalDate.endOf('month');\n        }\n        winston.debug(\"renewalDate: \", renewalDate)\n\n        return \"quotes:\" + type + \":\" + this.project._id + \":\" + renewalDate.format('M/D/YYYY');\n        // return \"quotes:\" + type + \":\" + this.project._id + \":\" + renewalDate.format('MM/DD/YYYY');\n        // return \"quotes:\" + type + \":\" + this.project._id + \":\" + renewalDate.toLocaleString();\n    }\n\n    // async _generateKey(object, type) {\n\n    //     winston.debug(\"generateKey object \", object)\n    //     winston.debug(\"generateKey type \" + type)\n    //     let subscriptionDate;\n\n    //     if (this.project.isActiveSubscription === true) {\n    //         if (this.project.profile.subStart) {\n    //             subscriptionDate = this.project.profile.subStart;\n    //         } else {\n    //             // it should never happen\n    //             winston.error(\"Error: quote manager - isActiveSubscription is true but subStart does not exists.\")\n    //         }\n    //     } else {\n    //         if (this.project.profile.subEnd) {\n    //             subscriptionDate = this.project.profile.subEnd;\n    //         } else {\n    //             subscriptionDate = this.project.createdAt;\n    //         }\n    //     }\n\n    //     let objectDate = object.createdAt;\n    //     winston.debug(\"objectDate \" + objectDate);\n\n    //     // converts date in timestamps and transform from ms to s\n    //     const objectDateTimestamp = ceil(objectDate.getTime() / 1000);\n    //     const subscriptionDateTimestamp = ceil(subscriptionDate.getTime() / 1000);\n\n    //     let ndays = (objectDateTimestamp - subscriptionDateTimestamp) / 86400;  // 86400 is the number of seconds in 1 day\n    //     let nmonths = floor(ndays / 30); // number of month to add to the initial subscription date;\n\n    //     let date = new Date(subscriptionDate);\n    //     date.setMonth(date.getMonth() + nmonths);\n\n    //     return \"quotes:\" + type + \":\" + this.project._id + \":\" + date.toLocaleDateString();\n    // }\n\n    /**\n     * Get current quote for a single type (tokens or request or ...)\n     */\n    async getCurrentQuote(project, object, type) {\n        this.project = project;\n        let key = await this.generateKey(object, type);\n        winston.verbose(\"[QuoteManager] getCurrentQuote key: \" + key);\n\n        let quote = await this.tdCache.get(key);\n        return Number(quote);\n    }\n\n    /**\n     * Get quotes for all types (tokens and request and ...)\n     */\n    async getAllQuotes(project, obj) {\n\n        this.project = project;\n\n        let quotes = {}\n        for (let type of typesList) {\n\n            let key = await this.generateKey(obj, type);\n            let quote = await this.tdCache.get(key);\n\n            quotes[type] = {\n                quote: Number(quote)\n            };\n        }\n        return quotes;\n    }\n\n    /**\n     * Perform a check on a single type.\n     * Returns TRUE if the limit is not reached --> operation can be performed\n     * Returns FALSE if the limit is reached --> operation can't be performed\n     */\n    async checkQuote(project, object, type) {\n\n        winston.verbose(\"checkQuote type \" + type);\n        if (quotes_enabled === false) {\n            winston.verbose(\"QUOTES DISABLED - checkQuote for type \" + type);\n            return true;\n        }\n\n        this.project = project;\n        let limits = await this.getPlanLimits();\n        winston.verbose(\"limits for current plan: \", limits)\n\n        let quote = await this.getCurrentQuote(project, object, type);\n        winston.verbose(\"getCurrentQuote resp: \" + quote)\n\n        if (quote == null) {\n            return true;\n        }\n\n        if (quote < limits[type]) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    async checkQuoteForAlert(project, object, type) {\n\n        if (quotes_enabled === false) {\n            winston.verbose(\"QUOTES DISABLED - checkQuote for type \" + type);\n            return (null, null);\n        }\n\n        this.project = project;\n        let limits = await this.getPlanLimits();\n        winston.verbose(\"limits for current plan: \", limits)\n\n        let quote = await this.getCurrentQuote(project, object, type);\n        winston.verbose(\"getCurrentQuote resp: \", quote)\n\n        let data = {\n            limits: limits,\n            quote: quote\n        }\n\n        return data;\n    }\n\n    async sendEmailIfQuotaExceeded(project, object, type, key) {\n\n        let data = await this.checkQuoteForAlert(project, object, type);\n        let limits = data.limits;\n        let limit = data.limits[type];\n        let quote = data.quote;\n\n        const checkpoint = await this.percentageCalculator(limit, quote);\n        if (checkpoint == 0) {\n            return;\n        }\n        winston.verbose(\"checkpoint perc: \", checkpoint);\n\n        // Generate redis key\n        let nKey = key + \":notify:\" + checkpoint;\n        let result = await this.tdCache.get(nKey);\n        if (!result) {\n\n            let allQuotes = await this.getAllQuotes(project, object);\n            let quotes = await this.generateQuotesObject(allQuotes, limits);\n\n            let data = {\n                id_project: project._id,\n                project_name: project.name,\n                type: type,\n                checkpoint: checkpoint,\n                quotes: quotes\n            }\n\n            emailEvent.emit('email.send.quote.checkpoint', data);\n            await this.tdCache.set(nKey, 'true', { EX: 2592000 }); //seconds in one month = 2592000\n        } else {\n            winston.verbose(\"Quota checkpoint reached email already sent.\")\n        }\n\n    }\n\n    async percentageCalculator(limit, quote) {\n\n        let p = (quote / limit) * 100;\n\n        if (p >= 100) { return 100; }\n        if (p >= 95) { return 95; }\n        if (p >= 75) { return 75; }\n        if (p >= 50) { return 50; }\n\n        return 0;\n\n    }\n\n    async invalidateCheckpointKeys(project, obj) {\n\n        this.project = project;\n        winston.verbose(\"invalidateCheckpointKeys project \" + project._id);\n        let requests_key = await this.generateKey(obj, 'requests');\n        let tokens_key = await this.generateKey(obj, 'tokens');\n        let email_key = await this.generateKey(obj, 'email');\n\n        let checkpoints = ['50', '75', '95', '100']\n\n        checkpoints.forEach(async (checkpoint) => {\n            let nrequests_key = requests_key + \":notify:\" + checkpoint;\n            let ntokens_key = tokens_key + \":notify:\" + checkpoint;\n            let nemail_key = email_key + \":notify:\" + checkpoint;\n\n            winston.verbose(\"invalidateCheckpointKeys nrequests_key: \" + nrequests_key);\n            winston.verbose(\"invalidateCheckpointKeys ntokens_key: \" + ntokens_key);\n            winston.verbose(\"invalidateCheckpointKeys nemail_key: \" + nemail_key);\n\n            this.tdCache.del(nrequests_key);\n            this.tdCache.del(ntokens_key);\n            this.tdCache.del(nemail_key);\n\n            return true;\n        })\n\n    }\n\n    async generateQuotesObject(quotes, limits) {\n        let quotes_obj = {\n            requests: {\n                quote: quotes.requests.quote,\n                perc: ((quotes.requests.quote / limits['requests']) * 100).toFixed(1)\n            },\n            tokens: {\n                quote: quotes.tokens.quote,\n                perc: ((quotes.tokens.quote / limits['tokens']) * 100).toFixed(1)\n            },\n            email: {\n                quote: quotes.email.quote,\n                perc: ((quotes.email.quote / limits['email']) * 100).toFixed(1)\n            }\n        }\n        return quotes_obj\n    }\n\n\n    async getPlanLimits(project) {\n\n        if (project) {\n            this.project = project\n        };\n\n        let limits;\n        const plan = this.project.profile.name;\n\n        if (this.project.profile.type === 'payment') {\n\n            if (this.project.isActiveSubscription === false) {\n                limits = PLANS_LIST.SANDBOX;\n                return limits;\n\n            }\n\n            switch (plan) {\n                case 'Starter':\n                    limits = PLANS_LIST.STARTER\n                    break;\n                case 'Pro':\n                    limits = PLANS_LIST.PRO\n                    break;\n                case 'Business':\n                    limits = PLANS_LIST.BUSINESS\n                    break;\n                case 'Basic':\n                    limits = PLANS_LIST.BASIC;\n                    break;\n                case 'Premium':\n                    limits = PLANS_LIST.PREMIUM;\n                    break;\n                case 'Team':\n                    limits = PLANS_LIST.TEAM;\n                    break;\n                case 'Custom':\n                    limits = PLANS_LIST.CUSTOM;\n                    break;\n                case 'Growth':  // OLD PLAN\n                    limits = PLANS_LIST.BASIC\n                    break;\n                case 'Scale':   // OLD PLAN\n                    limits = PLANS_LIST.PREMIUM\n                    break;\n                case 'Plus':    // OLD PLAN\n                    limits = PLANS_LIST.CUSTOM\n                    break;\n                default:\n                    limits = PLANS_LIST.FREE_TRIAL;\n            }\n\n        } else {\n\n            if (this.project.trialExpired === false) {\n                limits = PLANS_LIST.FREE_TRIAL\n            } else {\n                limits = PLANS_LIST.SANDBOX;\n            }\n\n        }\n\n        if (this.project?.profile?.quotes) {\n            let profile_quotes = this.project?.profile?.quotes;\n            const merged_quotes = Object.assign({}, limits, profile_quotes);\n            winston.verbose(\"Custom Limits: \", limits)\n            return merged_quotes;\n        } else {\n            winston.verbose(\"Default Limits: \", limits)\n            return limits;\n        }\n    }\n\n    async getCurrentSlot(project) {\n        let subscriptionDate;\n        if (project.isActiveSubscription === true) {\n            if (project.profile.subStart) {\n                subscriptionDate = moment(project.profile.subStart);\n                winston.debug(\"Subscription date from subStart: \" + subscriptionDate.toISOString());\n            } else {\n                // it should never happen\n                winston.error(\"Error: quote manager - isActiveSubscription is true but subStart does not exists.\")\n            }\n        } else {\n            if (project.profile.subEnd) {\n                subscriptionDate = moment(project.profile.subEnd);\n                winston.debug(\"Subscription date from subEnd: \" + subscriptionDate.toISOString());\n            } else {\n                subscriptionDate = moment(project.createdAt);\n                winston.debug(\"Subscription date from project createdAt: \" + subscriptionDate.toISOString());\n            }\n        }\n\n        let now = moment();\n        winston.debug(\"now: \", now);\n\n        let diffInMonths = now.diff(subscriptionDate, 'months');\n        winston.debug(\"diffInMonths: \", diffInMonths)\n\n        let renewalDate = subscriptionDate.clone().add(diffInMonths, 'months').startOf('day');\n        winston.debug(\"renewalDate: \", renewalDate)\n\n        let slotEnd = subscriptionDate.clone().add(diffInMonths + 1, 'month');\n        slotEnd.subtract(1, 'day').endOf('day')\n        winston.debug(\"slotEnd: \", slotEnd)\n\n        // let slot = {\n        //     startDate: renewalDate.format('DD/MM/YYYY'),\n        //     endDate: slotEnd.format('DD/MM/YYYY')\n        // }\n        let slot = {\n            startDate: renewalDate,\n            endDate: slotEnd\n        }\n\n        return slot;\n    }\n\n\n\n    start() {\n        winston.verbose('QuoteManager start');\n\n        if (process.env.QUOTES_ENABLED !== undefined) {\n            if (process.env.QUOTES_ENABLED === false || process.env.QUOTES_ENABLED === 'false') {\n                quotes_enabled = false;\n            }\n        }\n\n        winston.info(\"QUOTES ENABLED: \" + quotes_enabled);\n\n        // TODO - Try to generalize to avoid repetition\n        let incrementEventHandler = (object) => { }\n        let checkEventHandler = (object) => { }\n\n\n        // REQUESTS EVENTS - START\n        // requestEvent.on('request.create.quote.before', async (payload) => {\n        //     let result = await this.checkQuote(payload.project, payload.request, 'requests');\n        //     if (result == true) {\n        //         winston.info(\"Limit not reached - a request can be created\")\n        //     } else {\n        //         winston.info(\"Requests limit reached for the current plan!\")\n        //     }\n        //     return result;\n        // });\n\n        requestEvent.on('request.create.quote', async (payload) => {\n            if (quotes_enabled === true) {\n                winston.verbose(\"request.create.quote event catched\");\n                let result = await this.incrementRequestsCount(payload.project, payload.request);\n                return result;\n            } else {\n                winston.verbose(\"QUOTES DISABLED - request.create.quote event\")\n            }\n        })\n\n        requestEvent.on('request.close.quote', async (payload) => {\n            if (quotes_enabled === true) {\n                winston.verbose(\"request.close.quote event catched\");\n                let result = await this.incrementVoiceDurationCount(payload.project, payload.request);\n                return result;\n            } else {\n                winston.verbose(\"QUOTES DISABLED - request.close.quote event\")\n            }\n        })\n        // REQUESTS EVENTS - END\n\n\n        // MESSAGES EVENTS - START\n        // messageEvent.on('message.create.quote.before', async (payload) => {\n        //     let result = await this.checkQuote(payload.project, payload.message, 'messages');\n        //     if (result == true) {\n        //         winston.info(\"Limit not reached - a message can be created\")\n        //     } else {\n        //         winston.info(\"Messages limit reached for the current plan!\")\n        //     }\n        //     return result;\n        // })\n\n        messageEvent.on('message.create.quote', async (payload) => {\n            if (quotes_enabled === true) {\n                winston.verbose(\"message.create.quote event catched\");\n                let result = await this.incrementMessagesCount(payload.project, payload.message);\n                return result;\n            } else {\n                winston.verbose(\"QUOTES DISABLED - message.create.quote event\")\n            }\n        })\n        // MESSAGES EVENTS - END\n\n\n        // EMAIL EVENTS - START - Warning! Can't be used for check quote\n        // emailEvent.on('email.send.before', async (payload) => {\n        //     let result = await this.checkQuote(payload.project, payload.email, 'email');\n        //     if (result == true) {\n        //         winston.info(\"Limit not reached - a message can be created\")\n        //     } else {\n        //         winston.info(\"Email limit reached for the current plan!\")\n        //     }\n        //     return result;\n        // })\n\n        emailEvent.on('email.send.quote', async (payload) => {\n            if (quotes_enabled === true) {\n                winston.verbose(\"email.send event catched\");\n                let result = await this.incrementEmailCount(payload.project, payload.email);\n                return result;\n            } else {\n                winston.verbose(\"QUOTES DISABLED - email.send event\")\n            }\n        })\n        // EMAIL EVENTS - END\n    }\n\n\n}\n\nmodule.exports = { QuoteManager };"
  },
  {
    "path": "services/RateManager.js",
    "content": "const project = require(\"../models/project\");\n\n\nclass RateManager {\n\n    constructor(config) {\n\n        if (!config) {\n            throw new Error('config is mandatory')\n        }\n\n        if (!config.tdCache) {\n            throw new Error('config.tdCache is mandatory')\n        }\n\n        this.tdCache = config.tdCache;\n\n    \n        // Default rates \n        this.defaultRates = {\n            webhook: {\n                capacity: parseInt(process.env.BUCKET_WH_CAPACITY) || 10,\n                refill_rate: (parseInt(process.env.BUCKET_WH_REFILL_RATE_PER_MIN) || 10) / 60\n            },\n            message: {\n                capacity: parseInt(process.env.BUCKET_MSG_CAPACITY) || 100,\n                refill_rate: (parseInt(process.env.BUCKET_MSG_REFILL_RATE_PER_MIN) || 100) / 60\n            },\n            block: {\n                capacity: parseInt(process.env.BUCKET_BLK_CAPACITY) || 100,\n                refill_rate: (parseInt(process.env.BUCKET_BLK_REFILL_RATE_PER_MIN) || 100) / 60\n            }\n        }\n    }\n\n    async canExecute(projectOrId, id, type) {\n        \n        let project = projectOrId;\n        if (typeof projectOrId === 'string') {\n            project = await this.loadProject(projectOrId);\n            id = projectOrId;\n        }\n        \n        const config = await this.getRateConfig(project, type);\n        const key = `bucket:${type}:${id}`\n\n        let bucket = await this.getBucket(key, type, project);\n        let current_tokens = bucket.tokens;\n        let elapsed = (new Date() - new Date(bucket.timestamp)) / 1000;\n        let tokens = Math.min(config.capacity, current_tokens + (elapsed * config.refill_rate));\n\n        if (tokens > 0) {\n            tokens -= 1;\n            bucket.tokens = tokens;\n            bucket.timestamp = new Date();\n            this.setBucket(key, bucket)\n            return true;\n        } else {\n            bucket.timestamp = new Date();\n            return false;\n        }\n    }\n\n    async getRateConfig(project, type) {\n\n        const baseConfig = this.defaultRates[type];\n        if (!project) {\n            return baseConfig;\n        }\n\n        const custom = project?.profile?.customization?.rates?.[type];\n        if (!custom) {\n            return baseConfig;\n        }\n\n        return {\n            ...baseConfig,\n            ...custom\n        }\n    }\n\n    async setBucket(key, bucket) {\n        const bucket_string = JSON.stringify(bucket);\n        await this.tdCache.set(key, bucket_string, { EX: 600 });\n    }\n\n    async getBucket(key, type, project) {\n        let bucket = await this.tdCache.get(key);\n        if (bucket) {\n            return JSON.parse(bucket);\n        }\n        bucket = await this.createBucket(type, project);\n        return bucket;\n    }\n\n    async createBucket(type, project) {\n        const config = await this.getRateConfig(project, type)\n        return {\n            tokens: config.capacity,\n            timestamp: new Date()\n        }\n    }\n\n    async loadProject(id_project) {\n        // Hint: implement redis cache also for project\n        try {\n            return project.findById(id_project);\n        } catch (err) {\n            winston.error(\"(RateManager) Error getting project \", err);\n            return null;\n        }\n    }\n\n}\n\nmodule.exports = RateManager;"
  },
  {
    "path": "services/Scheduler.js",
    "content": "let JobManager = require(\"jobs-worker-queued\");\nlet winston = require('../config/winston');\n\nlet jobManager;\n\nclass Scheduler {\n\n    constructor(config) {\n\n        if (!config) {\n            throw new Error('(Scheduler) config is mandatory');\n        }\n\n        if (!config.jobManager) {\n            throw new Error('(Scheduler) config.jobManager is mandatory');\n        }\n\n        this.jobManager = config.jobManager;\n    }\n\n    trainSchedule(data, callback) {\n\n        winston.debug(\"(trainScheduler) data: \", data);\n        this.jobManager.publish(data, (err, ok) => {\n            let response_data = { success: true, message: \"Scheduled\" };\n            if (callback) {\n                callback(err, response_data);\n            }\n        });\n    }\n\n    tagSchedule(data, callback) {\n\n        winston.debug(\"(tagScheduler) data: \", data);\n        try {\n            this.jobManager.publish(data);\n            let response_data = { success: true, message: \"Scheduled\" };\n            if (callback) {\n                callback(null, response_data);\n            }\n        } catch(err) {\n            let response_data = { success: false, message: \"Task not scheduled\" };\n            if (callback) {\n                callback(err, response_data);\n            }\n        }\n    \n    }\n    \n}\n\nmodule.exports = { Scheduler };"
  },
  {
    "path": "services/aiManager.js",
    "content": "const { Namespace, KB, Engine } = require('../models/kb_setting');\nconst Integrations = require(\"../models/integrations\");\nconst aiService = require(\"./aiService\");\nconst { Scheduler } = require(\"./Scheduler\");\nconst { default: Sitemapper } = require('sitemapper');\nconst winston = require('../config/winston');\nconst configGlobal = require('../config/global');\nconst _ = require('lodash');\nconst JobManager = require('../utils/jobs-worker-queue-manager/JobManagerV2');\n\n// Constants\nconst apiUrl = process.env.API_URL || configGlobal.apiUrl;\nconst KB_WEBHOOK_TOKEN = process.env.KB_WEBHOOK_TOKEN || 'kbcustomtoken';\nconst AMQP_MANAGER_URL = process.env.AMQP_MANAGER_URL || 'amqp://localhost';\nconst JOB_TOPIC_EXCHANGE = process.env.JOB_TOPIC_EXCHANGE_TRAIN || 'tiledesk-trainer';\nconst JOB_TOPIC_EXCHANGE_HYBRID = process.env.JOB_TOPIC_EXCHANGE_TRAIN_HYBRID || 'tiledesk-trainer-hybrid';\n\n// Default engine configuration\nconst default_engine = require('../config/kb/engine');\nconst default_engine_hybrid = require('../config/kb/engine.hybrid');\nconst default_embedding = require('../config/kb/embedding');\nconst integrationService = require('./integrationService');\nconst situatedContext = require('../config/kb/situatedContext');\n\n// Job managers\nlet jobManager = new JobManager(AMQP_MANAGER_URL, {\n  debug: false,\n  topic: JOB_TOPIC_EXCHANGE,\n  exchange: JOB_TOPIC_EXCHANGE\n})\n\nlet jobManagerHybrid = new JobManager(AMQP_MANAGER_URL, {\n  debug: false,\n  topic: JOB_TOPIC_EXCHANGE_HYBRID,\n  exchange: JOB_TOPIC_EXCHANGE_HYBRID\n})\n\n// Connect job managers\njobManager.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"aiManager jobManager connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"aiManager jobManager - ConnectPublisher done with status: \", status);\n  }\n});\n\njobManagerHybrid.connectAndStartPublisher((status, error) => {\n  if (error) {\n    winston.error(\"aiManager jobManagerHybrid connectAndStartPublisher error: \", error);\n  } else {\n    winston.info(\"aiManager jobManagerHybrid - ConnectPublisher done with status: \", status);\n  }\n});\n\nclass AiManager {\n\n  constructor() { }\n\n  async addMultipleUrls(namespace, urls, options) {\n    return new Promise(async (resolve, reject) => {\n\n      let kbs = urls.map((url) => {\n        let kb = {\n          id_project: namespace.id_project,\n          name: url,\n          source: url,\n          type: 'url',\n          content: \"\",\n          namespace: namespace.id,\n          status: -1,\n          scrape_type: options.scrape_type,\n          scrape_options: options.scrape_options,\n          refresh_rate: options.refresh_rate,\n          ...(options.sitemap_origin_id && { sitemap_origin_id: options.sitemap_origin_id }),\n          ...(options.sitemap_origin && { sitemap_origin: options.sitemap_origin }),\n          ...(options.tags && { tags: options.tags }),\n        }\n        return kb;\n      })\n\n      let operations = kbs.map(doc => {\n        return {\n          updateOne: {\n            filter: { id_project: doc.id_project, type: 'url', source: doc.source, namespace: namespace.id },\n            update: doc,\n            upsert: true,\n            returnOriginal: false\n          }\n        }\n      })\n\n      this.saveBulk(operations, kbs, namespace.id_project, namespace.id).then( async (result) => {\n        let hybrid = namespace.hybrid;\n        let engine = namespace.engine || default_engine;\n        let embedding = namespace.embedding || default_embedding;\n        embedding.api_key = process.env.EMBEDDING_API_KEY || process.env.GPTKEY;\n\n        let situated_context = this.normalizeSituatedContext();\n\n        let webhook = apiUrl + '/webhook/kb/status?token=' + KB_WEBHOOK_TOKEN;\n\n        let resources = result.map(({ name, status, __v, createdAt, updatedAt, id_project, ...keepAttrs }) => keepAttrs)\n        resources = resources.map(({ _id, scrape_options, ...rest }) => {\n          return { \n            id: _id, \n            webhook: webhook, \n            parameters_scrape_type_4: scrape_options, \n            embedding: embedding, \n            engine: engine, \n            hybrid: hybrid, \n            ...(situated_context && { situated_context }),\n            ...rest}\n        });\n\n        winston.verbose(\"resources to be sent to worker: \", resources);\n\n        if (process.env.NODE_ENV === 'test') {\n          resolve({ result, schedule_json: resources });\n          return;\n        }\n  \n        this.scheduleScrape(resources, hybrid);\n        resolve(result);\n\n      }).catch((err) => {\n        winston.error(\"Error save contents in bulk: \", err);\n        reject(err);\n      })\n    })\n  }\n\n  async scheduleSitemap(namespace, sitemap_content, options) {\n    return new Promise((resolve, reject) => {\n\n      const situated_context = this.normalizeSituatedContext();\n\n      let kb = {\n        id: sitemap_content._id,\n        source: sitemap_content.source,\n        type: 'sitemap',\n        content: \"\",\n        namespace: namespace.id,\n        refresh_rate: options.refresh_rate,\n        engine: namespace.engine,\n        embedding: namespace.embedding,\n        hybrid: namespace.hybrid,\n        ...(situated_context && { situated_context }),\n      }\n\n      if (process.env.NODE_ENV === 'test') {\n        resolve(kb);\n        return;\n      }\n\n      this.scheduleScrape([kb], namespace.hybrid);\n      resolve(kb);\n\n    })\n  }\n\n  async updateSitemap(id_project, namespace, old_sitemap, new_sitemap) {\n\n    let fieldsToCheck = ['scrape_type', 'scrape_options', 'refresh_rate', 'tags'];\n    let { stop } = this.shouldStop(old_sitemap, new_sitemap, fieldsToCheck);\n\n    let updated_sitemap;\n    try {\n      updated_sitemap = await KB.findOneAndUpdate({ id_project, namespace: namespace.id, _id: old_sitemap._id }, new_sitemap, { new: true });\n    } catch (err) {\n      winston.error(\"Error updating sitemap: \", err);\n      throw err;\n    }\n\n    if (stop) {\n      return;\n    }\n\n    // Find all url contents with sitemap_origin_id\n    let urlContents = await KB.find({ id_project, namespace: namespace.id, sitemap_origin_id: old_sitemap._id }).lean().exec();\n    if (urlContents.length === 0) {\n      winston.error(\"No url contents found with sitemap_origin_id: \", old_sitemap._id);\n      throw new Error(\"No url contents found with sitemap_origin_id: \" + old_sitemap._id);\n    }\n\n    // Remove all url contents found with sitemap_origin_id\n    try {\n      await this.removeMultipleContents(namespace, urlContents);\n    } catch (err) {\n      winston.error(\"Error removing multiple contents: \", err);\n      throw err;\n    }\n\n    // Recreate all url contents with sitemap_origin_id\n    let result;\n    try {\n      result = await this.addMultipleUrls(namespace, urlContents.map(urlContent => urlContent.source), {\n        sitemap_origin_id: updated_sitemap._id,\n        sitemap_origin: updated_sitemap.source,\n        scrape_type: updated_sitemap.scrape_type,\n        scrape_options: updated_sitemap.scrape_options,\n        refresh_rate: updated_sitemap.refresh_rate,\n        tags: updated_sitemap.tags,\n      });\n    } catch (err) {\n      winston.error(\"Error recreating multiple contents: \", err);\n      throw err;\n    }\n\n    return result;\n\n  }\n\n  \n\n  shouldStop(oldObj, newObj, fieldsToCheck) {\n    for (const field of fieldsToCheck) {\n      const oldValue = _.get(oldObj, field);\n      const newValue = _.get(newObj, field);\n  \n      if (!_.isEqual(oldValue, newValue)) {\n        return { stop: false };\n      }\n    }\n  \n    return { stop: true };\n  }\n\n  async checkNamespace(id_project, namespace_id) {\n    return new Promise( async (resolve, reject) => {\n\n      let namespace = await Namespace.findOne({ id: namespace_id }).catch((err) => {\n        winston.error(\"Error getting namespace \", err);\n        reject(err);\n      })\n      if (!namespace) {\n        winston.warn(\"Namespace not found with id \" + namespace_id);\n        reject({ errorCode: 404, error: \"Namespace not found with id \" + namespace_id });\n      }\n      if (namespace.id_project !== id_project) {\n        winston.warn(\"Namespace not belonging to project \" + id_project);\n        reject({ errorCode: 403,  error: \"Namespace not belonging to project \" + id_project });\n      }\n\n      resolve(namespace);\n    })\n  }\n\n  async resolveLLMConfig(id_project, provider = 'openai', model) {\n\n    if (provider === 'ollama' || provider === 'vllm') {\n      try {\n        const integration = await integrationService.getIntegration(id_project, provider);\n        \n        if (!integration?.value?.url) {\n          throw { code: 422, error: `Server url for ${provider} is empty or invalid`}\n        }\n\n        return {\n          provider,\n          name: model,\n          url: integration.value.url,\n          api_key: integration.value.apikey || \"\"\n        }\n\n      } catch (err) {\n        throw { code: err.code, error: err.error }\n      }\n    }\n\n    try {\n      let key = await integrationService.getKeyFromIntegration(id_project, provider)\n\n      return {\n        provider,\n        name: model,\n        api_key: key\n      }\n\n    } catch (err) {\n      throw { code: err.code, error: err.error }\n    }\n\n  }\n\n  async checkQuotaAvailability(quoteManager, project, ncontents) {\n\n    return new Promise( async (resolve, reject) => {\n\n      let limits = await quoteManager.getPlanLimits(project);\n      let kbs_limit = limits.kbs;\n      winston.verbose(\"Limit of kbs for current plan: \" + kbs_limit);\n  \n      let kbs_count = await KB.countDocuments({ id_project: project._id }).exec();\n      winston.verbose(\"Kbs count: \" + kbs_count);\n  \n      if (kbs_count >= kbs_limit) {\n        reject({ errorCode: 403, error: \"Maximum number of resources reached for the current plan\", plan_limit: kbs_limit });\n      }\n\n      let total_count = kbs_count + ncontents;\n      if (total_count > kbs_limit) {\n        reject({ errorCode: 403, error: \"Cannot exceed the number of resources in the current plan\", plan_limit: kbs_limit });\n      }\n\n      resolve(true);\n    })\n  }\n\n  async fetchSitemap(sitemapUrl) {\n\n    return new Promise(async (resolve, reject) => {\n      const sitemap = new Sitemapper({\n        url: sitemapUrl,\n        timeout: 15000,\n        debug: false\n      });\n\n      const data = await sitemap.fetch().catch((err) => {\n        reject(err);\n      })\n\n      if (data.errors && data.errors.length > 0) {\n        reject(data.errors[0]);\n      }\n\n      const urls = Array.isArray(data.sites) ? data.sites : [];\n      resolve(urls);\n    })\n  }\n\n  async foundSitemapChanges(existingKbs, urls) {\n  \n    return new Promise( async (resolve, reject) => {\n      let existingIdsBySource = {};\n      existingKbs.forEach(doc => {\n        existingIdsBySource[doc.source] = doc._id;\n      });\n  \n      let addedUrls = urls.filter(url => !existingIdsBySource.hasOwnProperty(url));\n      let removedIds = existingKbs\n        .filter(doc => !urls.includes(doc.source))\n        .map(doc => doc._id);\n  \n      resolve({ addedUrls, removedIds });\n    })\n  }\n\n  async generateFilename(name) {\n    return name\n      .toLowerCase()\n      .trim()\n      .normalize(\"NFD\") // Normalize characters with accents\n      .replace(/[\\u0300-\\u036f]/g, \"\") // Removes diacritics (e.g. à becomes a)\n      .replace(/[^a-z0-9\\s-_]/g, \"\") // Remove special characters\n      .replace(/\\s+/g, \"-\") // Replaces spaces with dashes\n      .replace(/_/g, \"-\")\n      .replace(/-+/g, \"-\"); // Removes consecutive hyphens\n  }\n\n  async getKeyFromIntegrations(project_id) {\n\n    return new Promise( async (resolve) => {\n\n      let integration = await Integrations.findOne({ id_project: project_id, name: 'openai' }).catch((err) => {\n        winston.error(\"Unable to find openai integration for the current project \" + project_id);\n        resolve(null);\n      })\n      if (integration && integration.value && integration.value.apikey) {\n        resolve(integration.value.apikey);\n      } else {\n        resolve(null);\n      }\n    })\n  }\n\n  async removeMultipleContents(namespace, kbs) {\n  \n    return new Promise( async (resolve, reject) => {\n      \n      kbs.forEach((kb) => {\n        let data = {\n          id: kb._id,\n          namespace: kb.namespace,\n          engine: namespace.engine || default_engine\n        }\n  \n        aiService.deleteIndex(data).then((resp) => {\n          winston.debug(\"delete content response: \", resp);\n          if (resp.data.success === true) {\n            KB.findByIdAndDelete(kb._id, (err, deletedKb) => {\n              if (err) {\n                winston.error(\"Delete kb error: \", err);\n                reject(err);\n              }\n            })\n          } else {\n            KB.findOneAndDelete({ _id: kb._id, status: { $in: [-1, 400 ]}}, (err, deletedKb) => {\n              if (err) {\n                winston.error(\"Delete kb error: \", err);\n                reject(err);\n              }\n            })\n          }\n        })\n      })\n      resolve(true);\n    })\n  }\n\n  async saveBulk(operations, kbs, project_id, namespace) {\n\n    return new Promise((resolve, reject) => {\n      KB.bulkWrite(operations, { ordered: false }).then((result) => {\n        winston.verbose(\"bulkWrite operations result: \", result);\n\n        KB.find({ id_project: project_id, namespace: namespace, source: { $in: kbs.map(kb => kb.source) } }).lean().then((documents) => {\n          winston.debug(\"documents: \", documents);\n          resolve(documents)\n        }).catch((err) => {\n          winston.error(\"Error finding documents \", err)\n          reject(err);\n        })\n\n      }).catch((err) => {\n        reject(err);\n      })\n    })\n  }\n\n  setDefaultScrapeOptions() {\n    return {\n      tags_to_extract: [\"body\"],\n      unwanted_tags: [],\n      unwanted_classnames: []\n    }\n  }\n\n  async scheduleScrape(resources, hybrid) {\n\n    let scheduler;\n    if (hybrid) {\n      scheduler = new Scheduler({ jobManager: jobManagerHybrid });\n    } else {\n      scheduler = new Scheduler({ jobManager: jobManager });\n    }\n\n    if (!scheduler) {\n      winston.error(\"ScheduleScrape JobManager is not defined\");\n      return false;\n    }\n\n    resources.forEach(r => {\n      winston.debug(\"Schedule job with following data: \", r);\n      scheduler.trainSchedule(r, async (err, result) => {\n        let error_code = 100;\n        if (err) {\n          winston.error(\"Scheduling error: \", err);\n          error_code = 400;\n        } else {\n          winston.verbose(\"Scheduling result: \", result);\n        }\n        await this.updateStatus(r.id, error_code);\n      });\n    })\n\n    return true;\n  }\n\n  // from webhook\n  // async scheduleScrape(resources) {\n\n  //   let scheduler = new Scheduler({ jobManager: jobManager });\n  \n  //   resources.forEach(r => {\n  //     winston.debug(\"(Webhook) Schedule job with following data: \", r);\n  //     scheduler.trainSchedule(r, async (err, result) => {\n  //       if (err) {\n  //         winston.error(\"Scheduling error: \", err);\n  //       } else {\n  //         winston.info(\"Scheduling result: \", result);\n  //       }\n  //     });\n  //   })\n  \n  //   return true;\n  // }\n\n  async startScrape(data) {\n\n    if (!data.gptkey) {\n      let gptkey = process.env.GPTKEY;\n      if (!gptkey) {\n        return { error: \"GPT apikey undefined\" }\n      }\n      data.gptkey = gptkey;\n    }\n  \n    let status_updated = await this.updateStatus(data.id, 200);\n    winston.verbose(\"status of kb \" + data.id + \" updated: \" + status_updated);\n  \n    return new Promise((resolve, reject) => {\n      aiService.singleScrape(data).then(async (resp) => {\n        winston.debug(\"singleScrape resp: \", resp.data);\n        let status_updated = await this.updateStatus(data.id, 300);\n        winston.verbose(\"status of kb \" + data.id + \" updated: \" + status_updated);\n        resolve(resp.data);\n      }).catch( async (err) => {\n        winston.error(\"singleScrape err: \", err);\n        let error_message = err.response?.data?.error || \"An unexpected error occurred\";\n        let status_updated = await this.updateStatus(data.id, 400, error_message);\n        winston.verbose(\"status of kb \" + data.id + \" updated: \" + status_updated);\n        reject(err);\n      })\n    })\n  }\n\n  async statusConverter(status) {\n    return new Promise((resolve) => {\n\n      let td_status;\n      switch (status) {\n        case 0:\n          td_status = -1;\n          break;\n        case 2:\n          td_status = 200;\n          break;\n        case 3:\n          td_status = 300;\n          break;\n        case 4:\n          td_status = 400;\n          break;\n        default:\n          td_status = -1\n      }\n      resolve(td_status);\n    })\n  }\n\n  async updateStatus(id, status, error) {\n    return new Promise((resolve) => {\n\n      let update = {\n        status: status,\n        last_refresh: new Date()\n      }\n\n      if (error) {\n        update.last_error = {\n          timestamp: Date.now(),\n          message: error\n        }\n      }\n\n      KB.findByIdAndUpdate(id, update, { new: true }, (err, updatedKb) => {\n        if (err) {\n          resolve(false)\n        } else if (!updatedKb) {\n          winston.verbose(\"Unable to update status. Data source not found.\")\n          resolve(false)\n        } else {\n          winston.debug(\"updatedKb: \", updatedKb)\n          resolve(true);\n        }\n      })\n    })\n  }\n\n  normalizeSituatedContext() {\n    return situatedContext.enable\n      ? {\n        ...situatedContext,\n        api_key: process.env.SITUATED_CONTEXT_API_KEY || process.env.GPTKEY\n      }\n      : undefined;\n  }\n\n}\n\nconst aiManager = new AiManager();\n\nmodule.exports = aiManager;"
  },
  {
    "path": "services/aiReindexService.js",
    "content": "var winston = require('../config/winston');\nconst axios = require(\"axios\").default;\nrequire('dotenv').config();\n\nclass AiReindexService {\n\n    constructor() {\n        \n        this.BASE_URL = process.env.SCHEDULER_BASEURL;\n        winston.verbose(\"(ReindexScheduler) BASE_URL: \" + this.BASE_URL)\n        if (!this.BASE_URL) {\n            throw new Error(\"Missing paramter BASE_URL\");\n        }\n\n        this.TOKEN = process.env.SCHEDULER_TOKEN;\n        winston.verbose(\"(ReindexScheduler) TOKEN: \" + this.TOKEN)\n        if (!this.TOKEN) {\n            throw new Error(\"Missing paramter TOKEN\");\n        }\n\n        this.PROJECT = process.env.SCHEDULER_PROJECT;\n        winston.verbose(\"(ReindexScheduler) PROJECT: \" + this.PROJECT)\n        if (!this.PROJECT) {\n            throw new Error(\"Missing paramter PROJECT\");\n        }\n\n    }\n\n    async delete(content_id) {\n\n        return new Promise( async (resolve, reject) => {\n\n            let scheduler = await this.findScheduler(content_id).catch((err) => {\n                reject(err);\n            })\n\n            winston.verbose(\"(AiReindexService) delete() - scheduler: \", scheduler);\n\n            if (!scheduler) {\n                reject(\"Scheduler not found for content id \" + content_id);\n            }\n\n            let isOfflineS = await this.offlineScheduler(scheduler.id).catch((err) => {\n                reject(err);\n            })\n\n            winston.verbose(\"(AiReindexService) delete() - isOfflineS: \", isOfflineS)\n\n            let isOffline = await this.offlineWorkflow(scheduler.processDefinitionCode).catch((err) => {\n                reject(err);\n            })\n\n            winston.verbose(\"(AiReindexService) delete() - isOffline: \", isOffline)\n\n            let deleteResponse = await this.deleteWorkflow(scheduler.processDefinitionCode).catch((err) => {\n                reject(err);\n            })\n\n            winston.verbose(\"(AiReindexService) delete() - deleteResponse: \", deleteResponse)\n            \n            resolve(deleteResponse);\n        })\n    }\n    \n    async findScheduler(id) {\n\n        return new Promise( async (resolve, reject) => {\n            await axios({\n                url: `${this.BASE_URL}/projects/${this.PROJECT}/schedules/list`,\n                method: 'POST',\n                headers: {\n                    token: this.TOKEN\n                }\n            }).then((response) => {\n\n                let scheduler = response.data.data.find(s => s.processDefinitionName === \"auto-reindex-\" + id);\n                if (!scheduler) {\n                    resolve(null);\n                } else {\n                    resolve(scheduler);\n    \n                }\n\n            }).catch((err) => {\n                reject(err);\n            })\n        })\n    }\n    \n    async offlineScheduler(id) {\n    \n        return new Promise( async (resolve, reject) => {\n            await axios({\n                url: `${this.BASE_URL}/projects/${this.PROJECT}/schedules/${id}/offline`,\n                method: 'POST',\n                headers: {\n                    token: this.TOKEN\n                }\n            }).then((response) => {\n                resolve(response.data);\n            }).catch((err) => {\n                reject(err);\n            })\n        })\n    }\n    \n    async offlineWorkflow(code) {\n    \n        return new Promise( async (resolve, reject) => {\n    \n            const queryParams = {\n                releaseState: \"OFFLINE\"\n            }\n            await axios({\n                url: `${this.BASE_URL}/projects/${this.PROJECT}/process-definition/${code}/release`,\n                method: 'POST',\n                headers: {\n                    token: this.TOKEN\n                },\n                params: queryParams\n              }).then((response) => {\n                resolve(response.data);\n              }).catch((err) => {\n                reject(err);\n              })\n        })\n    }\n    \n    async deleteWorkflow(code) {\n    \n        return new Promise( async (resolve, reject) => {\n    \n            const queryParams = {\n                releaseState: \"OFFLINE\"\n            }\n            await axios({\n                url: `${this.BASE_URL}/projects/${this.PROJECT}/process-definition/${code}`,\n                method: 'DELETE',\n                headers: {\n                    token: this.TOKEN\n                }\n              }).then((response) => {\n                resolve(response.data);\n              }).catch((err) => {\n                reject(err);\n              })\n        })\n    }\n}\n\nmodule.exports = { AiReindexService };"
  },
  {
    "path": "services/aiService.js",
    "content": "var winston = require('../config/winston');\nconst axios = require(\"axios\").default;\nrequire('dotenv').config();\nconst jwt = require(\"jsonwebtoken\")\nconst FormData = require('form-data');\n\nlet openai_endpoint = process.env.OPENAI_ENDPOINT;\nlet kb_endpoint_train = process.env.KB_ENDPOINT_TRAIN;\nlet kb_endpoint_qa = process.env.KB_ENDPOINT_QA;\nlet kb_endpoint_train_gpu = process.env.KB_ENDPOINT_TRAIN_GPU;\nlet kb_endpoint_qa_gpu = process.env.KB_ENDPOINT_QA_GPU;\nlet secret = process.env.JWT_SECRET_KEY;\n\nclass AiService {\n\n  // OPEN AI\n  completions(data, gptkey) {\n\n    winston.debug(\"[OPENAI SERVICE] openai endpoint: \" + openai_endpoint);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: openai_endpoint + \"/chat/completions\",\n        headers: {\n          'Content-Type': 'application/json',\n          'Authorization': \"Bearer \" + gptkey\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n\n    })\n\n  }\n\n  transcription(buffer, gptkey) {\n\n    winston.debug(\"[OPENAI SERVICE] openai endpoint: \" + openai_endpoint);\n\n    return new Promise((resolve, reject) => {\n\n      const formData = new FormData();\n      formData.append('file', buffer, { filename: 'audiofile', contentType: 'audio/mpeg' });\n      formData.append('model', 'whisper-1');\n\n      axios({\n        url: openai_endpoint + \"/audio/transcriptions\",\n        headers: {\n          ...formData.getHeaders(),\n          'Authorization': \"Bearer \" + gptkey\n        },\n        data: formData,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n\n    })\n  }\n\n  // LLM\n  askllm(data) {\n    winston.debug(\"[OPENAI SERVICE] llm endpoint: \" + kb_endpoint_qa);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: kb_endpoint_qa + \"/ask\",\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody)\n      }).catch((err) => {\n        reject(err)\n      })\n    })\n  }\n\n  // KB - Deprecated?\n  // checkStatus(data) {\n  //   winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + kb_endpoint);\n\n  //   return new Promise((resolve, reject) => {\n\n  //     axios({\n  //       url: kb_endpoint + \"/scrape/status\",\n  //       headers: {\n  //         'Content-Type': 'application/json'\n  //       },\n  //       data: data,\n  //       method: 'POST'\n  //     }).then((resbody) => {\n  //       resolve(resbody);\n  //     }).catch((err) => {\n  //       reject(err);\n  //     })\n\n  //   })\n  // }\n\n  // Deprecated? \n  // startScrape(data) {\n  //   winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + kb_endpoint);\n\n  //   return new Promise((resolve, reject) => {\n\n  //     axios({\n  //       url: kb_endpoint + \"/scrape\",\n  //       headers: {\n  //         'Content-Type': 'application/json'\n  //       },\n  //       data: data,\n  //       method: 'POST'\n  //     }).then((resbody) => {\n  //       resolve(resbody);\n  //     }).catch((err) => {\n  //       reject(err);\n  //     })\n\n  //   })\n  // }\n\n  // Deprecated?  \n  // ask(data) {\n  //   winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + kb_endpoint);\n\n  //   return new Promise((resolve, reject) => {\n\n  //     axios({\n  //       url: kb_endpoint + \"/qa\",\n  //       headers: {\n  //         'Content-Type': 'application/json'\n  //       },\n  //       data: data,\n  //       method: 'POST'\n  //     }).then((resbody) => {\n  //       resolve(resbody);\n  //     }).catch((err) => {\n  //       reject(err);\n  //     })\n\n  //   })\n  // }\n\n  singleScrape(data) {\n    let base_url = kb_endpoint_train;\n    if (data.hybrid) {\n      base_url = kb_endpoint_train_gpu;\n    }\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: base_url + \"/scrape/single\",\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n\n    })\n  }\n\n  scrapeStatus(data) {\n    let base_url = kb_endpoint_train;\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: base_url + \"/scrape/status\",\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n    })\n  }\n\n  askNamespace(data) {\n    winston.debug(\"askNamespace data: \", data);\n    let base_url = kb_endpoint_qa;\n    if (data.hybrid || data.search_type === 'hybrid') {\n      base_url = kb_endpoint_qa_gpu;\n    }\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    const config = {\n      url: base_url + \"/qa\",\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      data: data,\n      method: 'POST'\n    };\n\n    return new Promise((resolve, reject) => {\n\n      axios(config).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n\n    })\n  }\n\n  /**\n   * Stream /qa from KB service. Uses Axios with responseType: 'stream'.\n   * Returns the raw Axios response (resp.data is the Node.js Readable stream).\n   */\n  askStream(data) {\n    winston.debug(\"askStream data: \", data);\n    let base_url = kb_endpoint_qa;\n    if (data.hybrid || data.search_type === 'hybrid') {\n      base_url = kb_endpoint_qa_gpu;\n    }\n    winston.debug(\"[OPENAI SERVICE] kb stream endpoint: \" + base_url);\n\n    return axios({\n      url: base_url + \"/qa\",\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      data: data,\n      method: 'POST',\n      responseType: 'stream'\n    });\n  }\n\n  getContentChunks(namespace_id, content_id, engine, hybrid) {\n    let base_url = kb_endpoint_train;\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    return new Promise((resolve, reject) => {\n\n      let payload = { engine: engine };\n      let token = jwt.sign(payload, secret);\n      axios({\n        url: base_url + \"/id/\" + content_id + \"/namespace/\" + namespace_id + \"/\" + token,\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        method: 'GET'\n      }).then((resbody) => {\n        resolve(resbody)\n      }).catch((err) => {\n        reject(err)\n      })\n    })\n  }\n\n  deleteIndex(data) {\n    let base_url = kb_endpoint_train;\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: base_url + \"/delete/id\",\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n    })\n  }\n\n  deleteNamespace(data) {\n    let base_url = kb_endpoint_train;\n    winston.debug(\"[OPENAI SERVICE] kb endpoint: \" + base_url);\n\n    return new Promise((resolve, reject) => {\n\n      axios({\n        url: base_url + \"/delete/namespace\",\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        data: data,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        reject(err);\n      })\n    })\n  }\n\n\n}\n\nconst aiService = new AiService();\n\nmodule.exports = aiService;"
  },
  {
    "path": "services/banUserNotifier.js",
    "content": "var messageService = require(\"./messageService\");\nvar projectEvent = require(\"../event/projectEvent\");\n\nvar winston = require('../config/winston');\n\nvar MessageConstants = require(\"../models/messageConstants\");\n\nclass BanUserNotifier {\n\n    listen() {\n        projectEvent.on(\"project.update.user.ban\", function(data) {\n            var project=data.project;\n            var banInfo = data.banInfo;\n\n            winston.debug(\"User Banned\");\n\n            var message = {\n                sender: 'system',\n                senderFullname: 'Bot',\n                recipient: banInfo.id,\n                recipientFullname: banInfo.id,\n                text: \"User Banned\",\n                id_project: project._id,\n                createdBy: \"system\",\n                attributes: {subtype:\"info\", messagelabel: {key: \"USER_BANNED\"} },\n                channel_type: MessageConstants.CHANNEL_TYPE.DIRECT,\n                status: MessageConstants.CHAT_MESSAGE_STATUS.SENDING,\n                // channel: {name: \"chat21\"}\n            };\n            messageService.save(message);\n            winston.info(\"User banned\", message);\n            // messageService.send(\n            //     'system', \n            //     'Bot',                                     \n            //     banInfo.id,\n            //     \"User Banned\", \n            //     project._id,\n            //     'system', \n            //     {subtype:\"info\"},\n            //     undefined,\n            //     undefined\n            // );\n        });\n        projectEvent.on(\"project.update.user.unban\", function(data) {\n            var project=data.project;\n            var banInfo = data.banInfo;\n\n            winston.debug(\"User UnBanned: \"+banInfo);\n\n            // var message = {\n            //     sender: 'system',\n            //     senderFullname: 'Bot',\n            //     recipient: banInfo,\n            //     recipientFullname: banInfo,\n            //     text: \"User Unbanned\",\n            //     id_project: project._id,\n            //     createdBy: \"system\",\n            //     attributes: {subtype:\"info\", messagelabel: {key: \"USER_BANNED\"}},\n            //     channel_type: MessageConstants.CHANNEL_TYPE.DIRECT,\n            //     status: MessageConstants.CHAT_MESSAGE_STATUS.SENDING,\n            // };\n            // messageService.save(message);\n            // winston.info(\"User UnBanned\", message);\n\n\n            \n\n            // messageService.send(\n            //     'system', \n            //     'Bot',                                     \n            //     banInfo.id,\n            //     \"User Unbanned\", \n            //     project._id,\n            //     'system', \n            //     {subtype:\"info\"},\n            //     undefined,\n            //     undefined\n            // );\n        });\n    }\n\n}\nvar banUserNotifier = new BanUserNotifier();\n\n\nmodule.exports = banUserNotifier;\n"
  },
  {
    "path": "services/bootDataLoader.js",
    "content": "'use strict';\n\nvar userService = require(\"./userService\");\nvar projectService = require(\"./projectService\");\n\nvar winston = require('../config/winston');\n\n\nclass BootDataLoader {\n\n\n  create() {\n    var that = this;\n      var email = process.env.ADMIN_EMAIL || \"admin@tiledesk.com\";\n      var password = process.env.ADMIN_PASSWORD || \"adminadmin\";\n      userService.signup(email, password, \"Administrator\", \" \", true)\n      .then(function (savedUser) {\n        winston.info(\"Created admin user with email \"+ email + \" and password \"+ password);\n        projectService.create(\"default\", savedUser.id, undefined).then(function (savedProject) {\n          winston.debug(\"Created default project\");\n        }).catch(function(err) {\n          winston.error(\"Error creating default project \", err);\n        });\n      }).catch(function(err) {\n        if (err.code == 11000) {\n          winston.info(\"Admin user already exists\");\n        }else {\n          winston.error(\"Error creating initial data \", err);\n        }\n    }); \n  }\n\n\n}\nvar bootDataLoader = new BootDataLoader();\n\n\nmodule.exports = bootDataLoader;\n"
  },
  {
    "path": "services/cacheEnabler.js",
    "content": "\nclass CacheEnabler {\n    constructor() {\n\n        // long TTL\n        this.trigger = true;\n        if (process.env.CACHE_TRIGGER_ENABLED==\"false\" || process.env.CACHE_TRIGGER_ENABLED==false) {\n            this.trigger = false;\n        }\n\n        // long TTL\n        this.subscription = true;\n        if (process.env.CACHE_SUBSCRIPTION_ENABLED==\"false\" || process.env.CACHE_SUBSCRIPTION_ENABLED==false) {\n            this.subscription = false;\n        }\n\n        //default TTL\n        this.project = true;\n        if (process.env.CACHE_PROJECT_ENABLED==\"false\" || process.env.CACHE_PROJECT_ENABLED==false) {\n            this.project = false;\n        }\n\n        //default TTL\n        this.request = true;\n        if (process.env.CACHE_REQUEST_ENABLED==\"false\" || process.env.CACHE_REQUEST_ENABLED==false) {\n            this.request = false;\n        }\n\n        this.faq_kb = true;\n        if (process.env.CACHE_FAQ_KB_ENABLED==\"false\" || process.env.CACHE_FAQ_KB_ENABLED==false) {\n            this.faq_kb = false;\n        }\n\n        \n        this.project_user = true;\n        if (process.env.CACHE_PROJECT_USER_ENABLED==\"false\" || process.env.CACHE_PROJECT_USER_ENABLED==false) {\n            this.project_user = false;\n        }\n\n        this.widgets = true;\n        if (process.env.CACHE_WIDGETS_ENABLED==\"false\" || process.env.CACHE_WIDGETS_ENABLED==false) {\n            this.widgets = false;\n        }\n\n        this.integrations = true;\n        if (process.env.CACHE_INTEGRATIONS_ENABLED==\"false\" || process.env.CACHE_INTEGRATIONS_ENABLED==false) {\n            this.integrations = false;\n        }\n\n        this.role = true;\n        if (process.env.CACHE_ROLE_ENABLED==\"false\" || process.env.CACHE_ROLE_ENABLED==false) {\n            this.role = false;\n        }\n\n       \n    }\n}\n\n\nvar cacheEnabler = new CacheEnabler();\n\n\nmodule.exports = cacheEnabler;"
  },
  {
    "path": "services/chatbotService.js",
    "content": "const axios = require(\"axios\").default;\nconst winston = require('../config/winston');\nlet Faq_kb = require(\"../models/faq_kb\");\n\nclass ChatbotService {\n\n  constructor() {}\n\n  async fork(id_faq_kb, api_url, token, project_id) {\n    winston.debug(\"(ChatbotService) fork\");\n\n    return await axios({\n      url: api_url + '/' + project_id + '/faq_kb/fork/'+id_faq_kb+\"?projectid=\"+project_id+\"&public=false&globals=true\",\n      headers: {\n        'Content-Type': 'application/json',\n        'Authorization': token\n      },\n      // data: chatbot,\n      method: 'POST'\n    }).then((resbody) => {\n      winston.debug(\"(ChatbotService) fork resbody: \", resbody.data);\n      return resbody.data;\n    }).catch((err) => {\n      winston.error(\"(ChatbotService) fork error \" + err);\n      throw err;\n    })\n\n  }\n\n  async getBotById(id_faq_kb, published, api_url, chatbot_templates_api_url, token, project_id, globals) {\n\n    winston.debug(\"(ChatbotService) getBotById\");\n\n    // private bot\n    if (published == \"false\") {\n\n      return await axios({\n        url: api_url + \"/\" + project_id + \"/faq_kb/exportjson/\" + id_faq_kb + \"?globals=\" + globals,\n        headers: {\n          'Content-Type': 'application/json',\n          'Authorization': token\n        },\n        method: 'GET'\n      }).then((resbody) => {\n        winston.debug(\"(ChatbotService) forking private chatbot \" + resbody.data.name)\n        let chatbot = resbody.data;\n        return chatbot;\n      }).catch((err) => {\n        winston.error('(ChatbotService) FAQ_KB EXPORTJSON ERROR ' + err);\n        throw err;\n      })\n\n    // public bot\n    } else {\n\n      return await axios({\n        url: chatbot_templates_api_url + \"/\" + id_faq_kb,\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        method: 'GET'\n      }).then((resbody) => {\n        winston.debug(\"(ChatbotService) forking public chatbot \" + resbody.data.name);\n        let chatbot = resbody.data;\n        return chatbot\n      }).catch((err) => {\n        winston.error('(ChatbotService) FAQ_KB CHATBOT TEMPLATES ERROR ' + err);\n        throw err;\n      })\n    }\n\n  }\n\n  async createBot(api_url, token, chatbot, project_id) {\n\n    winston.debug(\"(ChatbotService) createBot\");\n\n    return await axios({\n      url: api_url + '/' + project_id + '/faq_kb/',\n      headers: {\n        'Content-Type': 'application/json',\n        'Authorization': token\n      },\n      data: chatbot,\n      method: 'POST'\n    }).then((resbody) => {\n      winston.debug(\"(ChatbotService) createBot resbody: \", resbody.data);\n      return resbody.data;\n    }).catch((err) => {\n      winston.error(\"(ChatbotService) CREATE NEW CHATBOT ERROR \" + err);\n      throw err;\n    })\n\n  }\n\n  async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {\n  \n    winston.debug(\"(ChatbotService) importFaqs\");\n\n    return await axios({\n      url: api_url + '/' + project_id + '/faq_kb/importjson/' + id_faq_kb + \"?intentsOnly=true\",\n      headers: {\n        'Content-Type': 'application/json',\n        'Authorization': token\n      },\n      data: chatbot,\n      method: 'POST'\n    }).then((resbody) => {\n      winston.debug(\"(ChatbotService) importFaqs resbody: \", resbody.data);\n      return resbody.data;\n    }).catch((err) => {\n      winston.error(\"(ChatbotService) IMPORT FAQS ERROR \" + err);\n      throw err;\n    })\n  }\n\n  async setModified(chatbot_id, modified) {\n    return Faq_kb.findByIdAndUpdate(chatbot_id, { modified: modified }).then((faqKb) => {\n        winston.debug(\"(ChatbotService) set chatbot to modified response: \", faqKb);\n        return true;\n      }).catch((err) => {\n        return Promise.reject(err);\n      });\n  }\n}\n\nmodule.exports = { ChatbotService }"
  },
  {
    "path": "services/departmentService.js",
    "content": "\nvar Department = require(\"../models/department\");\nvar Project_user = require(\"../models/project_user\");\nvar Project = require(\"../models/project\");\nvar Group = require(\"../models/group\");\nvar operatingHoursService = require(\"./operatingHoursService\");\nvar winston = require('../config/winston');\nconst departmentEvent = require('../event/departmentEvent');\nconst Request = require('../models/request');\nconst RoleConstants = require ('../models/roleConstants')\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar cacheUtil = require(\"../utils/cacheUtil\");\n\nclass DepartmentService {\n\n  createDefault(project_id, createdBy) {\n    return this.create('Default Department', project_id, 'assigned', createdBy, true);\n  }\n\n  create(name, id_project, routing, createdBy, isdefault) {\n\n    if (!isdefault) {\n      isdefault = false;\n    }\n    \n    var that = this;\n    return new Promise(function (resolve, reject) {\n        var newDepartment = new Department({\n          routing: routing,\n          name: name,\n          id_project: id_project,\n          default: isdefault,\n          createdBy: createdBy,\n          updatedBy: createdBy\n        });\n    \n        return newDepartment.save(function (err, savedDepartment) {\n          if (err) {\n            winston.error('--- > ERROR ', err);\n            reject(err);\n          }\n          winston.verbose('Default Department created', savedDepartment.toObject());\n          return resolve(savedDepartment);\n        });\n      });\n  }\n\n  nextOperator (array, index) {\n    winston.debug('array: ', array);\n    winston.debug('index: ' + index);\n\n    index = index || 0;\n  \n    if (array === undefined || array === null)\n      array = [];\n    else if (!Array.isArray(array))\n      throw new Error('Expecting argument to RoundRound to be an Array');\n  \n    // return function () {\n        index++;\n      if (index >= array.length) index = 0;\n      winston.debug('index: ' + index);\n      return array[index];\n    // };\n}\n\n\nroundRobin(operatorSelectedEvent) {\n\n  var that = this;\n \n\n  return new Promise(function (resolve, reject) {\n\n    if (operatorSelectedEvent.department.routing !== 'assigned') {       \n      winston.debug('It isnt an assigned request');  \n      return resolve(operatorSelectedEvent);\n    }\n\n    // db.getCollection('requests').find({id_project: \"5c12662488379d0015753c49\", participants: { $exists: true, $ne: [] }}).sort({_id:-1}).limit(1)\n    \n      // https://stackoverflow.com/questions/14789684/find-mongodb-records-where-array-field-is-not-empty\n      let query = {id_project: operatorSelectedEvent.id_project, \n        hasBot:false, preflight:false, status: { $gt: 100 }, \n        participants: { $exists: true, $ne: [] }};\n      \n      winston.debug('query', query);            \n\n      // let lastRequests = await \n      // requestcachefarequi nocachepopulatereqired\n      Request.find(query).sort({_id:-1}).limit(1).exec(function (err, lastRequests) {  // cache_attention  use_lean use_select\n          if (err) {\n              winston.error('Error getting request for RoundRobinOperator', err); \n              return reject(err);\n          }\n         \n          \n          winston.debug('lastRequests',lastRequests); \n\n          if (lastRequests.length==0) {\n              winston.debug('roundRobin lastRequest not found. fall back to random info',operatorSelectedEvent); \n              winston.verbose('roundRobin lastRequest not found. fall back to random'); \n              //first request use default random algoritm\n              // return 0;\n              return resolve(operatorSelectedEvent);\n          }\n\n          // var start = Date.now();\n          // var res = sleep(5000);\n          // var end = Date.now();\n          // // res is the actual time that we slept for\n          // console.log(res + ' ~= ' + (end - start) + ' ~= 1000');\n\n\n          let lastRequest = lastRequests[0];\n          winston.debug('lastRequest:'+ JSON.stringify(lastRequest)); \n\n          let lastOperatorId = lastRequest.participants[0];\n          winston.debug('lastOperatorId: ' + lastOperatorId);\n\n\n          // BUGFIX (node:74274) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'id_user' of undefined\n          //   at /Users/andrealeo/dev/chat21/tiledesk-server/services/requestService.js:55:56\n          //   at processTicksAndRejections (internal/process/next_tick.js:81:5)\n          // (node:74274) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)          \n          if (operatorSelectedEvent.available_agents && operatorSelectedEvent.available_agents.length==0) {\n            winston.debug('operatorSelectedEvent.available_agents empty ', operatorSelectedEvent.available_agents);\n            return resolve(operatorSelectedEvent);\n          }          \n\n\n            //when the agent has a custom auth jwt. so he has uuid_user and not id_user\n          // error: uncaughtException: Cannot read property 'toString' of undefined\n          // 2021-10-14T08:54:00.099370+00:00 app[web.1]: TypeError: Cannot read property 'toString' of undefined\n          // 2021-10-14T08:54:00.099371+00:00 app[web.1]:     at /app/services/departmentService.js:130:119\n          // 2021-10-14T08:54:00.099372+00:00 app[web.1]:     at Array.findIndex (<anonymous>)\n          // 2021-10-14T08:54:00.099372+00:00 app[web.1]:     at /app/services/departmentService.js:130:74\n          // 2021-10-14T08:54:00.099372+00:00 app[web.1]:     at /app/node_modules/mongoose/lib/model.js:5074:18\n          // 2021-10-14T08:54:00.099381+00:00 app[web.1]:     at processTicksAndRejections (internal/process/task_q\n\n          // https://stackoverflow.com/questions/15997879/get-the-index-of-the-object-inside-an-array-matching-a-condition\n          let lastOperatorIndex = operatorSelectedEvent.available_agents.findIndex(projectUser =>  {\n                if (projectUser.id_user) {\n                  return projectUser.id_user.toString() === lastOperatorId;\n                } else { //when the agent has a custom auth jwt. so he has uuid_user and not id_user\n                  return projectUser.uuid_user === lastOperatorId;\n                }\n              }\n            );\n\n          \n          // if lastOperatorIndex is -1(last operator is not available)->  that.nextOperator increment index +1 so it's work\n\n\n  \n\n          winston.debug('lastOperatorIndex: ' + lastOperatorIndex);\n\n          winston.debug('operatorSelectedEvent.available_agents: ', operatorSelectedEvent.available_agents);\n\n          \n          let nextOperator = that.nextOperator(operatorSelectedEvent.available_agents, lastOperatorIndex);\n\n          \n          winston.debug('roundRobin nextOperator: ' ,nextOperator.toJSON());\n          \n          \n\n\n          // operatorSelectedEvent.operators = [{id_user: nextOperator.id_user}];\n          operatorSelectedEvent.operators = [nextOperator];\n\n          operatorSelectedEvent.lastOperatorId = lastOperatorId;\n          operatorSelectedEvent.lastRequest = lastRequest;\n          operatorSelectedEvent.lastOperatorIndex = lastOperatorIndex;\n\n\n          return resolve(operatorSelectedEvent);\n      });\n  \n    });\n}\n\n\n\ngetOperators(departmentid, projectid, nobot, disableWebHookCall, context) {\n\n  winston.debug('context0.0',context);\n\n  var that = this;\n  return new Promise(function (resolve, reject) {\n       // console.log(\"»»» »»» --> DEPT ID \", departmentid);\n\n\n    let q = Project.findOne({_id: projectid, status: 100})\n    if (cacheEnabler.project) { \n      q.cache(cacheUtil.longTTL, \"projects:id:\"+projectid)  //project_cache\n      winston.debug('project cache enabled for getOperators');\n    }\n    return q.exec(function(err, project){\n      if (err) {\n        winston.error('Project findById ', err);\n        return reject(err);\n      }\n      if (!project) {\n        winston.error(\"Project not found with id \", projectid);\n        return reject({ success: false, msg: \"Project not found with id \"});\n      }\n\n\n      // if not defined\n      // TODO questo lo abiliterei solo esplicitamete se si flagga opzione su progetto per performance\n      if (disableWebHookCall==undefined) {\n              //if pro enabled disableWebHookCall = false\n                              //secondo me qui manca un parentesi tonda per gli or\n        if (project.profile && (project.profile.type === 'free' && project.trialExpired === false) || (project.profile.type === 'payment' && project.isActiveSubscription === true)) {\n          // winston.info('disableWebHookCall pro');\n          disableWebHookCall = false;\n        } else {\n          disableWebHookCall = true;\n        }\n      }\n      \n   \n\n\n      let query;\n      if (departmentid == 'default' || departmentid == undefined) {\n        query = { default: true, id_project: projectid };\n      } else {\n        query = { _id: departmentid };\n      }\n       // console.log('query', query);\n      return Department.findOne(query).exec(function (err, department) {\n        // return Department.findOne(query).exec().then(function (department) {\n\n        if (err) {\n          winston.error('-- > 1 DEPT FIND BY ID ERR ', err)\n          return reject(err);\n        }\n        // console.log(\"department\", department);\n        if (!department) {\n          // TODO: error log removed due to attempt to reduces logs when no department is found\n          winston.verbose(\"Department not found for projectid: \"+ projectid +\" for query: \", query, context);\n          return reject({ success: false, msg: 'Department not found for projectid: '+ projectid +' for query: ' + JSON.stringify(query) });\n        }\n        // console.log('OPERATORS - »»» DETECTED ROUTING ', department.routing)\n        // console.log('OPERATORS - »»» DEPARTMENT - ID BOT ', department.id_bot)\n\n        // start code FOR DEBUG\n        // NOTE: TO TEST '?nobot = true' see in the tiledesk dashboard: mongodb-department.service > testAssignesFunction\n        if (nobot) {\n          // console.log('nobot IS == true ? ', nobot)\n          // console.log('»»»» »»»» nobot is == TRUE - JUMP TO ASSIGNED / POOLED ')\n        } else if (!nobot) {\n          // console.log('nobot IS != true ', nobot)\n          if ((department.id_bot == null || department.id_bot == undefined)) {\n            // console.log('»»»» »»»» BOT IS UNDEFINED or NULL and nobot is != TRUE - JUMP TO ASSIGNED / POOLED')\n          } else {\n            // console.log('»»»» »»»» BOT EXIST and nobot is != TRUE - ASSIGN THE SELECTED BOT ')\n          }\n        }\n        // /.end code FOR DEBUG \n\n        // IF EXIST THE BOT AND nobot IS NOT UNDEFINED IN OPERATORS IS RETURNED THE ID OF THE BOT\n        if ((department.id_bot != null || department.id_bot != undefined) && (!nobot)) {\n\n          // if (department.id_group == null || department.id_group == undefined) {\n          // MAKE X 'BOT' AS FOR 'ASSIGNED' AND 'POOLED': IF THERE IS A GROUP THE BOT WILL BE VISIBLE ONLY BY THE GROUP MEMBERS \n          // OTHERWISE THE BOT WILL BE VISIBLE TO ALL USERS (BECAUSE THERE IS NO GROUP)\n\n          // console.log('OPERATORS - »»»» BOT IS DEFINED - !!! DEPT HAS NOT GROUP ID')\n          // console.log('OPERATORS - »»»» BOT IS DEFINED -> ID BOT', department.id_bot);\n          // console.log('OPERATORS - »»»» nobot ', nobot)\n          winston.debug(\"main_flow_cache_2 departmentService project users\");\n          \n          \n          // rolequery\n          // var role = [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT];\n          // var qpu = Project_user.find({ id_project: projectid, role: { $in : role }, status: \"active\" });\n          var qpu = Project_user.find({ id_project: projectid, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" });\n\n          \n          // use this. $in doesn't use index very well\n          // var qpu = Project_user.findOne({ id_project: projectid, $or: [{ \"role\": RoleConstants.AGENT }, { \"role\": RoleConstants.SUPERVISOR }, { \"role\":  RoleConstants.ADMIN }, { \"role\": RoleConstants.OWNER }], status: \"active\" })\n          \n          if (cacheEnabler.project_user) {\n            qpu.cache(cacheUtil.queryTTL, projectid+\":project_users:query:teammates:available\") //request_cache\n            winston.debug('project_user cache enabled');\n          }\n  \n  \n          return qpu.exec(function (err, project_users) {\n            if (err) {\n              winston.error('-- > 2 DEPT FIND BY ID ERR ', err)\n              return reject(err);\n            }\n            // console.log('OPERATORS - BOT IS DEFINED - MEMBERS ', project_users)\n            // console.log('OPERATORS - BOT IS DEFINED - MEMBERS LENGHT ', project_users.length);\n\n            // getAvailableOperatorsWithOperatingHours: IN BASE ALLE 'OPERATING HOURS' DEL PROGETTO ESEGUE \n            // getAvailableOperator CHE RITORNA I PROJECT USER DISPONIBILI\n            return that.getAvailableOperatorsWithOperatingHours(project_users, projectid).then(function (_available_agents) {\n\n              // console.log(\"D -> [ OPERATORS - BOT IS DEFINED ] -> AVAILABLE PROJECT-USERS: \", _available_agents);\n\n              // here subscription notifier??              \n              return resolve ({ \n                              department: department, available_agents: _available_agents, agents: project_users, \n                              id_bot:department.id_bot, project: project,\n                              context: context,\n                              // botprefix\n                              operators: [{ id_user: 'bot_' + department.id_bot }] \n                             });\n            }).catch(function (error) {\n\n              // winston.error(\"Write failed: \", error);\n\n              winston.error(\"Error D -> [ OPERATORS - BOT IS DEFINED ] -> AVAILABLE PROJECT-USERS: \", error);\n\n              return reject(error);\n            });\n            \n          });\n        }\n\n        else { // if (department.routing === 'assigned' || department.routing === 'pooled') {\n          // console.log('OPERATORS - routing ', department.routing, ' - PRJCT-ID ', projectid)\n          // console.log('OPERATORS - routing ', department.routing, ' - DEPT GROUP-ID ', department.id_group)\n\n\n          /* ---------------------------------------------------------------------------------\n          *  findProjectUsersAllAndAvailableWithOperatingHours return: \n          *  * available_agents (available project users considering personal availability in the range of the operating hours) \n          *  * agents (i.e., all the project users) \n          *  * operators (i.e. the id of a user selected random from the available project users considering personal availability in the range of the operating hours)\n          * --------------------------------------------------------------------------------*/\n         winston.debug(\"context0\",context);\n          return that.findProjectUsersAllAndAvailableWithOperatingHours(projectid, department, disableWebHookCall, project, context).then(function (value) {\n\n            // console.log('D-0 -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) - ROUTING - ', department.routing, '] ', value);\n            value['department'] = department\n            return resolve(value);\n\n          }).catch(function (error) {\n            winston.error('D-0 -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) - ROUTING - ', department.routing, ' ] -> ERROR: ', error);\n            return reject(error);\n          });\n        }\n      });\n    });\n  });\n};\n\n findProjectUsersAllAndAvailableWithOperatingHours(projectid, department, disableWebHookCall, project, context) {\n  var that = this;\n\n  return new Promise(function (resolve, reject) {\n    // console.log('D-1 -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) - ROUTING - ', department.routing, ' ], - ID GROUP', department.id_group);\n\n    if (department.id_group != null) {\n\n      return resolve(that.findProjectUsersAllAndAvailableWithOperatingHours_group(projectid, department, disableWebHookCall, project, context));\n\n    } else {\n\n      return resolve(that.findProjectUsersAllAndAvailableWithOperatingHours_nogroup(projectid, department, disableWebHookCall, project, context));\n\n    }\n\n  });\n};\n\n findProjectUsersAllAndAvailableWithOperatingHours_group(projectid, department, disableWebHookCall, project, context) {\n  var that = this;\n\n  return new Promise(function (resolve, reject) {\n    return Group.find({ _id: department.id_group, $or: [ { enabled: true }, { enabled: { $exists: false } } ] }).exec(function (err, group) {\n      if (err) {\n        winston.error('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> ERR ', err)\n        return reject(err);\n      }\n      if (group) {\n        // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> GROUP FOUND:: ', group);\n        // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> MEMBERS LENGHT: ', group[0].members.length);\n        // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> MEMBERS ID: ', group[0].members);\n\n   \n\n        // rolequery\n        \n         return Project_user.find({ id_project: projectid, id_user: { $in : group[0].members}, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" }).exec(function (err, project_users) {          \n        //  return Project_user.find({ id_project: projectid, id_user: { $in : group[0].members}, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" }).exec(function (err, project_users) {         \n\n          // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> PROJECT ID ', projectid);\n          if (err) {\n            // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> PROJECT USER - ERR ', err);\n            return reject(err);\n          }\n          winston.debug(\"project_users\",project_users);\n          \n          if (project_users && project_users.length > 0) {\n            // console.log('D-2 GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> PROJECT USER (IN THE GROUP) LENGHT ', project_users.length);\n\n            return that.getAvailableOperatorsWithOperatingHours(project_users, projectid).then(function (_available_agents) {\n              var _available_agents = _available_agents\n              // console.log('D-3 NO GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> AVAILABLE AGENT ', _available_agents);\n\n              var selectedoperator = []\n              if (department.routing === 'assigned') {                \n                selectedoperator = that.getRandomAvailableOperator(_available_agents);\n              }\n\n              let objectToReturn = { available_agents: _available_agents, agents: project_users, operators: selectedoperator, department: department, group: group, id_project: projectid, project: project,  context: context };\n\n              // var objectToReturnRoundRobin = objectToReturn;\n              that.roundRobin(objectToReturn).then(function(objectToReturnRoundRobin){\n\n                winston.debug(\"context2\",context);\n                departmentEvent.emit('operator.select.base1', {result:objectToReturnRoundRobin, disableWebHookCall: disableWebHookCall, resolve: resolve, reject: reject, context: context});\n\n                //is resolved by departmentEvent or SubscriptionNotifier\n                // return resolve(objectToReturnRoundRobin);\n              });\n              \n\n            }).catch(function (error) {\n\n              // winston.error(\"Write failed: \", error);\n              winston.error('D-3 -> [ findProjectUsersAllAndAvailableWithOperatingHours_group ] - AVAILABLE AGENT - ERROR ', error);\n\n              return reject(error);\n              //sendError(error, res);\n            });\n           \n          } else {\n            // here subscription notifier??\n            var objectToReturn = { available_agents: [], agents: [], operators: [], context: context };\n            return resolve(objectToReturn);\n          }\n\n        })\n      }\n    });\n  });\n}\n\n\n findProjectUsersAllAndAvailableWithOperatingHours_nogroup(projectid, department, disableWebHookCall, project, context) {\n\n  var that = this;\n\n  return new Promise(function (resolve, reject) {\n\n    // var role = [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT];\n// rolequery\n    var qpu = Project_user.find({ id_project: projectid, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" });\n    // var qpu = Project_user.find({ id_project: projectid, role: { $in : role }, status: \"active\" });\n          \n    // use this. $in doesn't use index very well    \n    if (cacheEnabler.project_user) {\n      qpu.cache(cacheUtil.queryTTL, projectid+\":project_users:query:teammates:available\") //request_cache\n      winston.debug('project_user cache enabled');\n    }\n    // return Project_user.find({ id_project: projectid , role: { $in : role }, status: \"active\" }).exec(function (err, project_users) {\n    return qpu.exec(function (err, project_users) {\n\n\n      if (err) {\n        winston.error('D-3 NO GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> ERR ', err)\n        return reject(err);\n      }\n      // console.log('D-3 NO GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] ->  MEMBERS LENGHT ', project_users.length)\n      // console.log('D-3 NO GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] ->  MEMBERS ', project_users)\n\n\n      if (project_users && project_users.length > 0) {\n        \n        return that.getAvailableOperatorsWithOperatingHours(project_users, projectid).then(function (_available_agents) {\n          var _available_agents = _available_agents\n          // console.log('D-3 NO GROUP -> [ FIND PROJECT USERS: ALL and AVAILABLE (with OH) ] -> AVAILABLE AGENT ', _available_agents);\n\n          var selectedoperator = []\n          if (department.routing === 'assigned') {\n            selectedoperator = that.getRandomAvailableOperator(_available_agents);\n          }\n\n          let objectToReturn = { available_agents: _available_agents, agents: project_users, operators: selectedoperator, department: department, id_project: projectid, project: project, context: context };\n\n          // var objectToReturnRoundRobin = objectToReturn;\n\n          that.roundRobin(objectToReturn).then(function(objectToReturnRoundRobin) {\n            winston.debug(\"context2\",context);\n            departmentEvent.emit('operator.select.base1', {result:objectToReturnRoundRobin,  disableWebHookCall: disableWebHookCall, resolve: resolve, reject: reject, context: context});\n\n            // attento qui            \n            // if (objectToReturnRoundRobin.department._id == \"5e5d40b2bd0a9b00179ff3cf\" ) {\n            //   objectToReturnRoundRobin.operators = [];\n            // }\n          \n\n            //is resolved by departmentEvent or SubscriptionNotifier\n            // return resolve(objectToReturnRoundRobin);\n          });\n          \n        }).catch(function (error) {\n\n          // winston.error(\"Write failed: \", error);\n          winston.error('D-3 -> [ findProjectUsersAllAndAvailableWithOperatingHours_nogroup ] - AVAILABLE AGENT - ERROR ', error);\n          return reject(error);\n\n        });\n\n       \n      } else {\n        // here subscription notifier??\n        let objectToReturn = { available_agents: [], agents: [], operators: [], context: context };\n        return resolve(objectToReturn);\n      }\n\n    });\n  });\n}\n\n\n\n getAvailableOperatorsWithOperatingHours(project_users, projectid) {\n\n  var that = this;\n\n\n  return new Promise(function (resolve, reject) {\n\n    if (project_users && project_users.length > 0) {\n\n      return operatingHoursService.projectIsOpenNow(projectid, function (isOpen, err) {\n        // console.log('D -> [ OHS ] -> [ GET AVAILABLE PROJECT-USER WITH OPERATING H ] -> PROJECT ID: ', projectid);\n        // console.log('D -> [ OHS ] -> [ GET AVAILABLE PROJECT-USER WITH OPERATING H ] -> IS OPEN THE PROJECT: ', isOpen);\n        // console.log('D -> [ OHS ] -> [ GET AVAILABLE PROJECT-USER WITH OPERATING H ] -> IS OPEN THE PROJECT - ERROR: ', err)\n\n        if (err) {\n          winston.error(err); \n          return reject(err);\n          // sendError(err, res);\n\n        } \n        \n        if (isOpen) {\n\n          var _available_agents = that.getAvailableOperator(project_users);\n\n          return resolve(_available_agents);\n        } else {\n          // console.logO ---> [ OHS ] -> PROJECT NOT FOUND(\"HERERERERERERE\");\n          return resolve([]);\n        }\n      });\n    } else {\n      return resolve([]);\n    }\n\n  });\n}\n\n// FILTER ALL THE PROJECT USERS FOR AVAILABLE = TRUE\n getAvailableOperator(project_users) {\n  var project_users_available = project_users.filter(function (projectUser) {\n    if (projectUser.user_available == true) {\n      return true;\n    }\n  });\n  // console.log('D -> [GET AVAILABLE PROJECT-USER ] - AVAILABLE PROJECT USERS (getAvailableOperator): ', project_users_available)\n  return project_users_available\n}\n\n\n\n \ngetDefaultDepartment(projectid) {\n  return new Promise(function (resolve, reject) {\n    let query = { default: true, id_project: projectid };\n    winston.debug('query ', query)\n    // console.log('query', query);\n    return Department.findOne(query).exec(function (err, department) {\n      // return Department.findOne(query).exec().then(function (department) {\n\n      if (err) {\n        winston.error('-- > 1 DEPT FIND BY ID ERR ', err)\n        return reject(err);\n      }\n      // console.log(\"department\", department);\n      if (!department) {\n        // TODO: error log removed due to attempt to reduces logs when no department is found\n        winston.verbose(\"Department not found for projectid: \"+ projectid +\" for query: \", query, context);\n        return reject({ success: false, msg: 'Department not found for projectid: '+ projectid +' for query: ' + JSON.stringify(query) });\n      }\n      winston.debug('department ', department);\n\n      return resolve(department);\n    });\n  });\n}\n\n getRandomAvailableOperator(project_users_available) {\n\n  // console.log('-- > OPERATORS [ getRandomAvailableOperator ] - PROJECT USER AVAILABLE LENGHT ', project_users_available.length);\n  if (project_users_available.length > 0) {\n\n\n    // new\n                                  // num between 0 and 1 * es 3 -> \n    // let randomIndex =  Math.round(Math.random() * project_users_available.length);\n    // // let randomIndex =  Math.floor(Math.random() * project_users_available.length);\n    \n    // console.log(\"randomIndex\",randomIndex);\n    // var operator = project_users_available[randomIndex];\n    // // console.log('OPERATORS - SELECTED MEMBER ID', operator.id_user);\n\n    var operator = project_users_available[Math.floor(Math.random() * project_users_available.length)];\n\n\n    return [{ id_user: operator.id_user }];\n    // return [operator];\n\n  }\n  else {\n\n    return []\n\n  }\n}\n\n  /**\n   * Checks if the group belongs to a department of the project\n   * @param {String} projectId\n   * @param {String} groupId\n   * @returns {Promise<Boolean>} true if the group belongs to a department of the project, otherwise false\n   */\n  async isGroupInProjectDepartment(projectId, groupId) {\n    try {\n      const department = await Department.findOne({ id_project: projectId, id_group: groupId });\n      return !!department;\n    } catch (err) {\n      winston.error('Error in isGroupInProjectDepartment', err);\n      return false;\n    }\n  }\n\n\n}\n\n\nvar departmentService = new DepartmentService();\n\n\nmodule.exports = departmentService;\n"
  },
  {
    "path": "services/emailService.js",
    "content": "'use strict';\n\nconst nodemailer = require('nodemailer');\nvar config = require('../config/email');\nvar configGlobal = require('../config/global');\nvar winston = require('../config/winston');\nvar marked = require('marked');\nvar handlebars = require('handlebars');\nvar encode = require('html-entities').encode;\nconst emailEvent = require('../event/emailEvent');\n\nconst createDOMPurify = require('dompurify');\nconst { JSDOM } = require('jsdom');\nconst window = new JSDOM('').window;\nconst DOMPurify = createDOMPurify(window);\n\nhandlebars.registerHelper('ifEquals', function (arg1, arg2, options) {\n  return (arg1 == arg2) ? options.fn(this) : options.inverse(this);\n});\n\nhandlebars.registerHelper('dateFormat', require('handlebars-dateformat'));\n\n\n// var options = {};\n// handlebars.registerHelper('markdown', markdown(options));\n\n// handlebars.registerHelper('ifCond', function(v1, v2, options) {\n//   if(v1 === v2) {\n//     return options.fn(this);\n//   }\n//   return options.inverse(this);\n// });\n\n\nvar fs = require('fs');\nvar appRoot = require('app-root-path');\n\nconst MaskData = require(\"maskdata\");\n\nconst maskOptions = {\n  // Character to mask the data. default value is '*'\n  maskWith: \"*\",\n  // If the starting 'n' digits needs to be unmasked\n  // Default value is 4\n  unmaskedStartDigits: 3, //Should be positive Integer\n  //If the ending 'n' digits needs to be unmasked\n  // Default value is 1\n  unmaskedEndDigits: 3 // Should be positive Integer\n};\n\n// const X_REQUEST_ID_HEADER_KEY = \"X-TILEDESK-REQUEST-ID\";\n// const X_TICKET_ID_HEADER_KEY = \"X-TILEDESK-TICKET-ID\";\n// const X_PROJECT_ID_HEADER_KEY = \"X-TILEDESK-PROJECT-ID\";\n\nconst MESSAGE_ID_DOMAIN = \"tiledesk.com\";\n\n// const hcustomization = require('../utils/hcustomization');\n\nclass EmailService {\n\n  constructor() {\n\n    this.enabled = false;\n    if (process.env.EMAIL_ENABLED === \"true\" || process.env.EMAIL_ENABLED === true) {\n      this.enabled = true;\n    }\n\n    winston.info('EmailService enabled: ' + this.enabled);\n\n    this.baseUrl = process.env.EMAIL_BASEURL || config.baseUrl;\n    winston.info('EmailService baseUrl: ' + this.baseUrl);\n\n    this.apiUrl = process.env.API_URL || configGlobal.apiUrl;\n    winston.info('EmailService apiUrl: ' + this.apiUrl);\n\n    this.from = process.env.EMAIL_FROM_ADDRESS || config.from;\n    winston.info('EmailService from email: ' + this.from);\n\n    this.bcc = process.env.EMAIL_BCC || config.bcc;\n    winston.info('EmailService bcc address: ' + this.bcc);\n\n    this.replyEnabled = config.replyEnabled;\n    if (process.env.EMAIL_REPLY_ENABLED === \"true\" || process.env.EMAIL_REPLY_ENABLED === true) {\n      this.replyEnabled = true;\n    }\n    winston.info('EmailService replyEnabled : ' + this.replyEnabled);\n\n    // this is used as fixed reply to url, but this is unused we always return support-group-dynamic\n    this.replyTo = process.env.EMAIL_REPLY_TO || config.replyTo;\n    winston.info('EmailService replyTo address: ' + this.replyTo);\n\n    this.inboundDomain = process.env.EMAIL_INBOUND_DOMAIN || config.inboundDomain;\n    winston.info('EmailService inboundDomain : ' + this.inboundDomain);\n\n    this.inboundDomainDomainWithAt = \"@\" + this.inboundDomain;\n    winston.verbose('EmailService inboundDomainDomainWithAt : ' + this.inboundDomainDomainWithAt);\n\n    this.pass = process.env.EMAIL_PASSWORD;\n\n    var maskedemailPassword;\n    if (this.pass) {\n      maskedemailPassword = MaskData.maskPhone(this.pass, maskOptions);\n    } else {\n      maskedemailPassword = this.pass;\n    }\n\n    winston.info('EmailService pass: ' + maskedemailPassword);\n\n    this.host = process.env.EMAIL_HOST || config.host;\n    winston.info('EmailService host: ' + this.host);\n\n    this.secure = false;\n    if (process.env.EMAIL_SECURE == \"true\" || process.env.EMAIL_SECURE == true) {\n      this.secure = true;\n    }\n    // this.secure  = process.env.EMAIL_SECURE || false;     \n    winston.info('EmailService secure: ' + this.secure);\n\n    this.user = process.env.EMAIL_USERNAME || config.username;\n    winston.info('EmailService username: ' + this.user);\n\n    this.port = process.env.EMAIL_PORT;  //default is 587\n    winston.info('EmailService port: ' + this.port);\n\n\n    this.markdown = true;\n    if (process.env.EMAIL_MARKDOWN == \"false\" || process.env.EMAIL_MARKDOWN == false) {\n      this.markdown = false;\n    }\n    // this.markdown = process.env.EMAIL_MARKDOWN || true;\n    winston.info('EmailService markdown: ' + this.markdown);\n\n    this.headers = {\n      // \"X-Mailer\": \"Tiledesk Mailer\",\n    }\n    winston.info('EmailService headers: ' + JSON.stringify(this.headers));\n\n    this.ccEnabled = false //cc creates loop when you send an email with cc: support@tiledesk.com -> Tiledesk generates an email with ticket id with in cc support@tiledesk.com that loop \n\n    if (process.env.EMAIL_CC_ENABLED === \"true\" || process.env.EMAIL_CC_ENABLED === true) {\n      this.ccEnabled = true;\n    }\n    winston.info('EmailService ccEnabled: ' + this.ccEnabled);\n\n    this.brand_name = \"Tiledesk\"\n    if (process.env.BRAND_NAME) {\n      this.brand_name = process.env.BRAND_NAME;\n    }\n\n  }\n\n  readTemplate(templateName, settings, environmentVariableKey) {\n    // aggiunsta questo\n    var that = this;\n    winston.debug('EmailService readTemplate: ' + templateName + ' environmentVariableKey:  ' + environmentVariableKey + ' setting ' + JSON.stringify(settings));\n\n    if (settings && settings.email && settings.email.templates) {\n\n      var templates = settings.email.templates;\n      winston.debug('EmailService templates: ', templates);\n\n      var templateDbName = templateName.replace(\".html\", \"\");\n      winston.debug('EmailService templateDbName: ' + templateDbName);\n\n\n      var template = templates[templateDbName];\n      winston.debug('EmailService template: ' + template);\n\n      if (template) {\n        // that.callback(template);\n        return new Promise(function (resolve, reject) {\n          return resolve(template);\n        });\n      } \n      else {\n        var envTemplate = process.env[environmentVariableKey];\n        winston.debug('EmailService envTemplate: ' + envTemplate);\n\n        if (envTemplate) {\n          winston.debug('EmailService return envTemplate: ' + envTemplate);\n\n          return envTemplate;\n        } else {\n          winston.debug('EmailService return file: ' + templateName);\n\n          return that.readTemplateFile(templateName);\n        }  \n      }\n      \n      // else {\n      //   return that.readTemplateFile(templateName);\n      // }\n    } else {\n      var envTemplate = process.env[environmentVariableKey];\n      winston.debug('EmailService envTemplate: ' + envTemplate);\n\n      if (envTemplate) {\n        winston.debug('EmailService return envTemplate: ' + envTemplate);\n\n        return envTemplate;\n      } else {\n        winston.debug('EmailService return file: ' + templateName);\n\n        return that.readTemplateFile(templateName);\n      }\n      \n    }\n  }\n  readTemplateFile(templateName) {\n    // var that = this;\n    return new Promise(function (resolve, reject) {\n      fs.readFile(appRoot + '/template/email/' + templateName, { encoding: 'utf-8' }, function (err, html) {\n        if (err) {\n          winston.error('error readTemplateFile getting ', err);\n          // callback(err);\n          return reject(err);\n        }\n        else {\n          // callback(null, html);\n          return resolve(html);\n        }\n      });\n    });\n  };\n\n\n  getTransport(configEmail) {\n\n    if (configEmail === undefined) {\n      configEmail = {\n        host: this.host,\n        port: this.port, // defaults to 587 if is secure is false or 465 if true\n        secure: this.secure,\n        user: this.user,\n        pass: this.pass\n      }\n      winston.debug(\"getTransport initialized with default\");\n    } else {\n      winston.verbose(\"getTransport custom\", configEmail);\n    }\n\n    winston.debug(\"getTransport configEmail: \" + JSON.stringify(configEmail));\n\n    let transport = {\n      host: configEmail.host,\n      port: configEmail.port, // defaults to 587 if is secure is false or 465 if true\n      secure: configEmail.secure,\n      auth: {\n        user: configEmail.user,\n        pass: configEmail.pass\n      },\n      // secureConnection: false,\n      // tls:{\n      //   ciphers:'SSLv3'\n      // },\n\n      // openssl genrsa -out dkim_private.pem 2048   \n      // openssl rsa -in dkim_private.pem -pubout -outform der 2>/dev/null | openssl base64 -A\n      // -> \n      // v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunT2EopDAYnHwAOHd33KhlzjUXJfhmA+fK+cG85i9Pm33oyv1NoGrOynsni0PO6j7oRxxHqs6EMDOw4I/Q0C7aWn20oBomJZehTOkCV2xpuPKESiRktCe/MIZqbkRdypis4jSkFfFFkBHwgkAg5tb11E9elJap0ed/lN5/XlpGedqoypKxp+nEabgYO5mBMMNKRvbHx0eQttRYyIaNkTuMbAaqs4y3TkHOpGvZTJsvUonVMGAstSCfUmXnjF38aKpgyTausTSsxHbaxh3ieUB4ex+svnvsJ4Uh5Skklr+bxLVEHeJN55rxmV67ytLg5XCRWqdKIcJHFvSlm2YwJfcwIDAQABMacAL\n      // testdkim._domainkey.tiledesk.com. 86400 IN TXT \"v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAunT2EopDAYnHwAOHd33KhlzjUXJfhmA+fK+cG85i9Pm33oyv1NoGrOynsni0PO6j7oRxxHqs6EMDOw4I/Q0C7aWn20oBomJZehTOkCV2xpuPKESiRktCe/MIZqbkRdypis4jSkFfFFkBHwgkAg5tb11E9elJap0ed/lN5/XlpGedqoypKxp+nEabgYO5mBMMNKRvbHx0eQttRYyIaNkTuMbAaqs4y3TkHOpGvZTJsvUonVMGAstSCfUmXnjF38aKpgyTausTSsxHbaxh3ieUB4ex+svnvsJ4Uh5Skklr+bxLVEHeJN55rxmV67ytLg5XCRWqdKIcJHFvSlm2YwJfcwIDAQABMacAL\"\n\n      // dkim: {\n      //   domainName: \"example.com\",\n      //   keySelector: \"2017\",\n      //   privateKey: \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBg...\",\n      //   cacheDir: \"/tmp\",\n      //   cacheTreshold: 100 * 1024\n      // }\n    };\n\n    winston.debug(\"getTransport transport: \", transport);\n\n    // create reusable transporter object using the default SMTP transport\n    let transporter = nodemailer.createTransport(transport);\n    return transporter;\n  }\n\n  // @deprecated\n  // send(to, subject, html) {\n  //   return this.sendMail({to:to, subject:subject, html:html});\n  // }\n\n  async send(mail, quoteEnabled, project, quoteManager) {\n    if (!this.enabled) {\n      winston.info('EmailService is disabled. Not sending email');\n      return 0;\n    }\n    if (process.env.NODE_ENV == 'test') {\n      return winston.warn(\"EmailService not sending email for testing\");\n    }\n\n    let payload = { project: project }\n    if (quoteEnabled && quoteEnabled === true) {\n      mail.createdAt = new Date();\n      payload.email = mail;\n\n      if (quoteManager) {\n        let result = await quoteManager.checkQuote(project, mail, 'email');\n        if (result === false) {\n          // Stop\n          winston.verbose('Unable to send email - Quota exceeded')\n          return false;\n        }\n      }\n    }\n\n\n    let mailOptions = {\n      from: mail.from || this.from, // sender address\n      to: mail.to,\n      cc: mail.cc,\n      // bcc: config.bcc,\n      replyTo: mail.replyTo || this.replyTo,\n      inReplyTo: mail.inReplyTo,\n      references: mail.references,\n      subject: mail.subject, // Subject line\n      text: mail.text, // plain text body\n      html: mail.html,\n\n      headers: mail.headers || this.headers,\n\n      messageId: mail.messageId,\n      sender: mail.sender\n    };\n\n    winston.debug('mailOptions', mailOptions);\n    winston.debug(' mail.config', mail.config);\n\n    if (!mail.to) {\n      // return winston.warn(\"EmailService send method. to field is not defined\", mailOptions);\n      return winston.warn(\"EmailService send method. to field is not defined\");\n    }\n\n    // send mail with defined transport object\n    this.getTransport(mail.config).sendMail(mailOptions, (error, info) => {\n      if (error) {\n        if (mail.callback) {\n          mail.callback(error, { info: info });\n        }\n        //return winston.error(\"Error sending email \", { error: error, mailConfig: mail.config, mailOptions: mailOptions });\n        return winston.error(\"Error sending email \", { error: error, mailConfig: mail.config });\n      }\n      winston.verbose('Email sent:', { info: info });\n      winston.debug('Email sent:', { info: info, mailOptions: mailOptions });\n\n      if (quoteEnabled && quoteEnabled === true) {\n        emailEvent.emit('email.send.quote', payload);\n        winston.verbose(\"email.send.quote event emitted\");\n      }\n\n      if (mail.callback) {\n        mail.callback(error, { info: info });\n      }\n\n      // Preview only available when sending through an Ethereal account\n      // winston.debug('Preview URL: %s', nodemailer.getTestMessageUrl(info));\n\n      // Message sent: <b658f8ca-6296-ccf4-8306-87d57a0b4321@example.com>\n      // Preview URL: https://ethereal.email/message/WaQKMgKddxQDoou...\n    });\n  }\n\n\n  async sendTest(to, configEmail, callback) {\n\n    var that = this;\n\n    // var html = await this.readTemplate('test.html', { \"email\": { \"templates\": { test: \"123\" } } }, \"EMAIL_TEST_HTML_TEMPLATE\");\n    var html = await this.readTemplate('test.html', undefined, \"EMAIL_TEST_HTML_TEMPLATE\");\n\n    var template = handlebars.compile(html);\n\n    var replacements = {\n    };\n\n    var html = template(replacements);\n\n    return that.send({ to: to, subject: `${this.brand_name} test email`, config: configEmail, html: html, callback: callback });\n\n  }\n\n\n\n  async sendNewAssignedRequestNotification(to, request, project) {\n\n    var that = this;\n\n    //if the request came from rabbit mq?\n    if (request.toJSON) {\n      request = request.toJSON();\n    }\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('assignedRequest.html', project.settings, \"EMAIL_ASSIGN_REQUEST_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    // passa anche tutti i messages in modo da stampare tutto\n    // Stampa anche contact.email\n\n\n    let msgText = request.first_text;//.replace(/[\\n\\r]/g, '<br>');\n    // winston.verbose(\"msgText: \" + msgText);\n    msgText = encode(msgText);\n    // winston.verbose(\"msgText: \" + msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n\n    winston.debug(\"msgText: \" + msgText);\n\n    var replacements = {\n      request: request,\n      project: project,\n      msgText: msgText,\n      baseScope: baseScope,\n      // tools: {marked:marked}    \n    };\n\n    winston.debug(\"replacements \", replacements);\n\n    var html = template(replacements);\n    winston.debug(\"html after: \" + html);\n\n\n    let messageId = \"notification\" + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) { //fai anche per gli altri\n      replyTo = request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (request) {\n\n      messageId = request.request_id + \"+\" + messageId;\n\n      if (request.attributes && request.attributes.email_replyTo) {\n        replyTo = request.attributes.email_replyTo;\n      }\n\n      headers = {\n        \"X-TILEDESK-PROJECT-ID\": project._id,\n        \"X-TILEDESK-REQUEST-ID\": request.request_id,\n        \"X-TILEDESK-TICKET-ID\": request.ticket_id,\n      };\n\n      winston.debug(\"messageId: \" + messageId);\n      winston.debug(\"replyTo: \" + replyTo);\n      winston.debug(\"email headers\", headers);\n    }\n\n    let inReplyTo;\n    let references;\n    if (request.attributes) {\n      if (request.attributes.email_messageId) {\n        inReplyTo = request.attributes.email_messageId;\n      }\n      if (request.attributes.email_references) {\n        references = request.attributes.email_references;\n      }\n    }\n    winston.debug(\"email inReplyTo: \" + inReplyTo);\n    winston.debug(\"email references: \" + references);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n    // troncare nome utnete e nome progetto a max 10 caratteri\n    // cambiare in [Nicky:Dashboard Support] Assigned Chat\n    // serve per aggiornare native... fai aggiornamento \n\n    let subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] New Assigned Chat`;\n\n    if (request.subject) {\n      subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] ${request.subject}`;\n    }\n\n    let subject = that.formatText(\"assignedRequestSubject\", subjectDef, request, project.settings);\n\n\n    // if (request.ticket_id) {\n    //   subject = `[Ticket #${request.ticket_id}] New Assigned Chat`;\n    // }\n\n    // if (request.ticket_id && request.subject) {\n    //   subject = `[Ticket #${request.ticket_id}] ${request.subject}`;\n    // }\n\n    that.send({\n      messageId: messageId,\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      subject: subject,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n    messageId = \"notification\" + messageId;\n\n\n    // togliere bcc \n    that.send({\n      messageId: messageId,\n      to: that.bcc,\n      replyTo: replyTo,\n      subject: subject + ` ${to}  - notification`,\n      html: html,\n      headers: headers\n    });\n\n\n  }\n\n\n  async sendNewAssignedAgentMessageEmailNotification(to, request, project, message) {\n\n    var that = this;\n\n    //if the request came from rabbit mq?\n    if (request.toJSON) {\n      request = request.toJSON();\n    }\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('assignedEmailMessage.html', project.settings, \"EMAIL_ASSIGN_MESSAGE_EMAIL_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    // passa anche tutti i messages in modo da stampare tutto\n    // Stampa anche contact.email\n\n    let msgText = message.text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n\n    var replacements = {\n      request: request,\n      project: project,\n      message: message,\n      msgText: msgText,\n      baseScope: baseScope\n    };\n\n    winston.debug(\"replacements \", replacements);\n\n    var html = template(replacements);\n    winston.debug(\"html after: \" + html);\n\n\n    let messageId = message._id + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = message.request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (message.request) {\n\n      messageId = message.request.request_id + \"+\" + messageId;\n\n      if (message.request.attributes && message.request.attributes.email_replyTo) {\n        replyTo = message.request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": message.request.request_id, \"X-TILEDESK-TICKET-ID\": message.request.ticket_id };\n\n      winston.debug(\"sendNewAssignedAgentMessageEmailNotification messageId: \" + messageId);\n      winston.debug(\"sendNewAssignedAgentMessageEmailNotification replyTo: \" + replyTo);\n      winston.debug(\"sendNewAssignedAgentMessageEmailNotification email headers\", headers);\n    }\n\n    let inReplyTo;\n    let references;\n    if (message.request.attributes) {\n      if (message.request.attributes.email_messageId) {\n        inReplyTo = message.request.attributes.email_messageId;\n      }\n      if (message.request.attributes.email_references) {\n        references = message.request.attributes.email_references;\n      }\n    }\n    winston.debug(\"sendNewAssignedAgentMessageEmailNotification email inReplyTo: \" + inReplyTo);\n    winston.debug(\"sendNewAssignedAgentMessageEmailNotification email references: \" + references);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"sendNewAssignedAgentMessageEmailNotification custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"sendNewAssignedAgentMessageEmailNotification custom from email setting found: \" + from);\n      }\n    }\n\n\n    let subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] New message`;\n\n    if (request.subject) {\n      subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] ${request.subject}`;\n    }\n    if (request.ticket_id) {\n      subjectDef = `[${this.brand_name} #${request.ticket_id}] New message`;\n    }\n\n    if (request.ticket_id && request.subject) {\n      subjectDef = `[Ticket #${request.ticket_id}] ${request.subject}`;\n    }\n\n    let subject = that.formatText(\"assignedEmailMessageSubject\", subjectDef, request, project.settings);\n\n\n\n    that.send({\n      messageId: messageId,\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      // inReplyTo: inReplyTo,???\n      // references: references,??\n      subject: subject,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n\n\n    messageId = \"notification\" + messageId;\n\n    that.send({\n      messageId: messageId,\n      to: that.bcc,\n      replyTo: replyTo,\n      subject: subject + ` - notification`,\n      html: html,\n      headers: headers\n    });\n\n\n  }\n\n\n  async sendNewPooledRequestNotification(to, request, project) {\n\n    //if the request came from rabbit mq?\n    if (request.toJSON) {\n      request = request.toJSON();\n    }\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var that = this;\n\n    var html = await this.readTemplate('pooledRequest.html', project.settings, \"EMAIL_POOLED_REQUEST_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    // passa anche tutti i messages in modo da stampare tutto\n    // Stampa anche contact.email\n\n    let msgText = request.first_text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n\n    var replacements = {\n      request: request,\n      project: project,\n      msgText: msgText,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n\n    let messageId = \"notification-pooled\" + new Date().getTime() + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (request) {\n\n      messageId = request.request_id + \"+\" + messageId;\n\n      if (request.attributes && request.attributes.email_replyTo) {\n        replyTo = request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": request.request_id, \"X-TILEDESK-TICKET-ID\": request.ticket_id };\n\n      winston.debug(\"sendNewPooledRequestNotification messageId: \" + messageId);\n      winston.debug(\"sendNewPooledRequestNotification replyTo: \" + replyTo);\n      winston.debug(\"sendNewPooledRequestNotification email headers\", headers);\n    }\n\n    let inReplyTo;\n    let references;\n    if (request.attributes) {\n      if (request.attributes.email_messageId) {\n        inReplyTo = request.attributes.email_messageId;\n      }\n      if (request.attributes.email_references) {\n        references = request.attributes.email_references;\n      }\n    }\n    winston.debug(\"sendNewPooledRequestNotification email inReplyTo: \" + inReplyTo);\n    winston.debug(\"sendNewPooledRequestNotification email references: \" + references);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"sendNewPooledRequestNotification custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"sendNewPooledRequestNotification custom from email setting found: \" + from);\n      }\n    }\n\n    let subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] New Pooled Chat`;\n\n    if (request.subject) {\n      subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] ${request.subject}`;\n    }\n\n    let subject = that.formatText(\"pooledRequestSubject\", subjectDef, request, project.settings);\n\n\n    // if (request.ticket_id) {\n    //   subject = `[Ticket #${request.ticket_id}] New Pooled Chat`;\n    // }\n\n    // if (request.ticket_id && request.subject) {\n    //   subject = `[Ticket #${request.ticket_id}] ${request.subject}`;\n    // }\n\n\n\n    that.send({\n      messageId: messageId,\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      subject: subject,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n    // this.send(that.bcc, `[TileDesk ${project ? project.name : '-'}] New Pooled Request`, html);\n\n  }\n\n\n\n\n\n  async sendNewPooledMessageEmailNotification(to, request, project, message) {\n\n    var that = this;\n\n\n    //if the request came from rabbit mq?\n    if (request.toJSON) {\n      request = request.toJSON();\n    }\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('pooledEmailMessage.html', project.settings, \"EMAIL_POOLED_MESSAGE_EMAIL_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    let msgText = message.text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n\n    // passa anche tutti i messages in modo da stampare tutto\n    // Stampa anche contact.email\n\n    var replacements = {\n      request: request,\n      project: project,\n      message: message,\n      msgText: msgText,\n      baseScope: baseScope\n    };\n\n    winston.debug(\"replacements \", replacements);\n\n    var html = template(replacements);\n    winston.debug(\"html after: \" + html);\n\n\n\n    let messageId = message._id + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = message.request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (message.request) {\n\n      messageId = message.request.request_id + \"+\" + messageId;\n\n      if (message.request.attributes && message.request.attributes.email_replyTo) {\n        replyTo = message.request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": message.request.request_id, \"X-TILEDESK-TICKET-ID\": message.request.ticket_id };\n\n      winston.debug(\"sendNewPooledMessageEmailNotification messageId: \" + messageId);\n      winston.debug(\"sendNewPooledMessageEmailNotification replyTo: \" + replyTo);\n      winston.debug(\"sendNewPooledMessageEmailNotification email headers\", headers);\n    }\n\n    let inReplyTo;\n    let references;\n    if (message.request.attributes) {\n      if (message.request.attributes.email_messageId) {\n        inReplyTo = message.request.attributes.email_messageId;\n      }\n      if (message.request.attributes.email_references) {\n        references = message.request.attributes.email_references;\n      }\n    }\n    winston.debug(\"sendNewPooledMessageEmailNotification email inReplyTo: \" + inReplyTo);\n    winston.debug(\"sendNewPooledMessageEmailNotification email references: \" + references);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"sendNewPooledMessageEmailNotification custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"sendNewPooledMessageEmailNotification custom from email setting found: \" + from);\n      }\n    }\n\n\n    let subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] New Message`;\n\n    if (request.subject) {\n      subjectDef = `[${this.brand_name} ${project ? project.name : '-'}] ${request.subject}`;\n    }\n    if (request.ticket_id) {\n      subjectDef = `[Ticket #${request.ticket_id}] New Message`;\n    }\n\n    if (request.ticket_id && request.subject) {\n      subjectDef = `[Ticket #${request.ticket_id}] ${request.subject}`;\n    }\n\n    let subject = that.formatText(\"pooledEmailMessageSubject\", subjectDef, request, project.settings);\n\n\n    that.send({\n      messageId: messageId,\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      // inReplyTo: inReplyTo,???\n      // references: references,??\n      subject: subject,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n\n\n    // messageId =  \"notification\" + messageId;\n\n    // that.send({\n    //   messageId: messageId,\n    //   to: that.bcc, \n    //   replyTo: replyTo,\n    //   subject: `[TileDesk ${project ? project.name : '-'}] - ${request.subject ? request.subject : 'New message'} - notification`, \n    //   html:html,\n    //   headers:headers \n    // });\n\n\n  }\n\n\n  async sendNewMessageNotification(to, message, project, tokenQueryString, sourcePage) {\n\n    var that = this;\n\n    //if the request came from rabbit mq?\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('newMessage.html', project.settings, \"EMAIL_NEW_MESSAGE_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    let msgText = message.text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n\n    var replacements = {\n      message: message,\n      project: project,\n      msgText: msgText,\n      seamlessPage: sourcePage,\n      tokenQueryString: tokenQueryString,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n    winston.debug(\"html: \" + html);\n\n\n    let messageId = message._id + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = message.request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (message.request) {\n\n      messageId = message.request.request_id + \"+\" + messageId;\n\n      if (message.request.attributes && message.request.attributes.email_replyTo) {\n        replyTo = message.request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": message.request.request_id, \"X-TILEDESK-TICKET-ID\": message.request.ticket_id };\n\n      winston.debug(\"messageId: \" + messageId);\n      winston.debug(\"replyTo: \" + replyTo);\n      winston.debug(\"email headers\", headers);\n    }\n\n    let inReplyTo;\n    let references;\n    winston.debug(\"message.request.attributes\", message.request.attributes);\n    if (message.request.attributes) {\n      if (message.request.attributes.email_messageId) {\n        inReplyTo = message.request.attributes.email_messageId;\n      }\n      if (message.request.attributes.email_references) {\n        references = message.request.attributes.email_references;\n      }\n    }\n    winston.debug(\"email inReplyTo: \" + inReplyTo);\n    winston.debug(\"email references: \" + references);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n\n    let subject = that.formatText(\"newMessageSubject\", `[${this.brand_name} ${project ? project.name : '-'}] New Offline Message`, message, project.settings);\n\n    that.send({\n      messageId: messageId,\n      // sender: message.senderFullname, //must be an email\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      inReplyTo: inReplyTo,\n      references: references,\n      subject: subject,    //TODO (anche per il cloud) aggiungere variabile env per cambiare i subjects\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n    messageId = \"notification\" + messageId;\n\n    that.send({\n      messageId: messageId,\n      // sender: message.senderFullname, //must be an email\n      to: that.bcc,\n      replyTo: replyTo,\n      inReplyTo: inReplyTo,\n      references: references,\n      subject: `[${this.brand_name} ${project ? project.name : '-'}] New Offline Message - notification`,\n      html: html,\n      headers: headers\n    });\n\n  }\n\n\n\n  async sendEmailChannelNotification(to, message, project, tokenQueryString, sourcePage) {\n\n    var that = this;\n\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('ticket.html', project.settings, \"EMAIL_TICKET_HTML_TEMPLATE\");\n    // this.readTemplateFile('ticket.txt', function(err, html) {\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    let msgText = message.text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n    winston.debug(\"baseScope: \" + JSON.stringify(baseScope));\n\n\n    var replacements = {\n      message: message,\n      project: project,\n      seamlessPage: sourcePage,\n      msgText: msgText,\n      tokenQueryString: tokenQueryString,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n    winston.debug(\"html: \" + html);\n\n\n\n\n    let messageId = message._id + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = message.request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (message.request) {\n\n      messageId = message.request.request_id + \"+\" + messageId;\n\n      if (message.request.attributes && message.request.attributes.email_replyTo) {\n        replyTo = message.request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": message.request.request_id, \"X-TILEDESK-TICKET-ID\": message.request.ticket_id };\n\n      winston.debug(\"messageId: \" + messageId);\n      winston.debug(\"replyTo: \" + replyTo);\n      winston.debug(\"email headers\", headers);\n    }\n\n\n    let inReplyTo;\n    let references;\n    let cc;\n    let ccString;\n\n    if (message.request && message.request.attributes) {\n      winston.debug(\"email message.request.attributes: \", message.request.attributes);\n\n      if (message.request.attributes.email_messageId) {\n        inReplyTo = message.request.attributes.email_messageId;\n      }\n      if (message.request.attributes.email_references) {\n        references = message.request.attributes.email_references;\n      }\n\n      if (that.ccEnabled == true) {\n        if (message.request.attributes.email_cc) {\n          cc = message.request.attributes.email_cc;\n        }\n        winston.debug(\"email message.request.attributes.email_ccStr: \" + message.request.attributes.email_ccStr);\n        if (message.request.attributes.email_ccStr != undefined) {\n          ccString = message.request.attributes.email_ccStr;\n          winston.debug(\"email set ccString\");\n        }\n      }\n\n    }\n    winston.debug(\"email inReplyTo: \" + inReplyTo);\n    winston.debug(\"email references: \" + references);\n    winston.debug(\"email cc: \", cc);\n    winston.debug(\"email ccString: \" + ccString);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n    //gmail uses subject\n    let subject = that.formatText(\"ticketSubject\", `R: ${message.request ? message.request.subject : '-'}`, message, project.settings);\n\n    //ocf \n    //prod                                                      //pre\n    // if (project._id ==\"6406e34727b57500120b1bd6\" || project._id == \"642c609f179910002cc56b3e\") {\n    //   subject = \"Richiesta di supporto #\" + message.request.ticket_id;\n    //   if (message.request.subject) {\n    //     subject = subject + \" - \" + message.request.subject;\n    //   } \n    //   // console.log(\"subject\",subject);\n    // }\n\n    // if (message.request && message.request.lead && message.request.lead.email) {\n    //   winston.info(\"message.request.lead.email: \" + message.request.lead.email);\n    //   replyTo = replyTo + \", \"+ message.request.lead.email;\n    // }\n\n    that.send({\n      messageId: messageId,\n      // sender: message.senderFullname, //must be an email\n      from: from,\n      to: to,\n      cc: ccString,\n      replyTo: replyTo,\n      inReplyTo: inReplyTo,\n      references: references,\n      // subject:`${message.request ? message.request.subject : '-'}`, \n      subject: subject,\n      text: html,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n    messageId = \"notification\" + messageId;\n\n    that.send({\n      messageId: messageId,\n      // sender: message.senderFullname, //must be an email\n      to: that.bcc,\n      replyTo: replyTo,\n      inReplyTo: inReplyTo,\n      references: references,\n      // subject: `${message.request ? message.request.subject : '-'} - notification`, \n      subject: `R: ${message.request ? message.request.subject : '-'} - notification`,\n      text: html,\n      html: html,\n      headers: headers\n    });\n\n  }\n\n\n\n\n\n\n\n\n\n\n\n  async sendFollowerNotification(to, message, project) {\n\n    var that = this;\n\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('newMessageFollower.html', project.settings, \"EMAIL_FOLLOWER_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    let msgText = message.text;//.replace(/[\\n\\r]/g, '<br>');\n    msgText = encode(msgText);\n    if (this.markdown) {\n      msgText = marked(msgText);\n    }\n\n    winston.debug(\"msgText: \" + msgText);\n    winston.debug(\"baseScope: \" + JSON.stringify(baseScope));\n\n\n    var replacements = {\n      message: message,\n      project: project,\n      msgText: msgText,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n    winston.debug(\"html: \" + html);\n\n    const fs = require('fs');\n    fs.writeFileSync('tem1111.html', html);\n\n\n\n    let messageId = message._id + \"@\" + MESSAGE_ID_DOMAIN;\n\n    let replyTo;\n    if (this.replyEnabled) {\n      replyTo = message.request.request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let headers;\n    if (message.request) {\n\n      messageId = message.request.request_id + \"+\" + messageId;\n\n      if (message.request.attributes && message.request.attributes.email_replyTo) {\n        replyTo = message.request.attributes.email_replyTo;\n      }\n\n      headers = { \"X-TILEDESK-PROJECT-ID\": project._id, \"X-TILEDESK-REQUEST-ID\": message.request.request_id, \"X-TILEDESK-TICKET-ID\": message.request.ticket_id };\n\n      winston.debug(\"messageId: \" + messageId);\n      winston.debug(\"replyTo: \" + replyTo);\n      winston.debug(\"email headers\", headers);\n    }\n\n\n    let inReplyTo;\n    let references;\n    let cc;\n    let ccString;\n\n    if (message.request && message.request.attributes) {\n      winston.debug(\"email message.request.attributes: \", message.request.attributes);\n\n      if (message.request.attributes.email_messageId) {\n        inReplyTo = message.request.attributes.email_messageId;\n      }\n      if (message.request.attributes.email_references) {\n        references = message.request.attributes.email_references;\n      }\n\n      if (that.ccEnabled == true) {\n        if (message.request.attributes.email_cc) {\n          cc = message.request.attributes.email_cc;\n        }\n        winston.debug(\"email message.request.attributes.email_ccStr: \" + message.request.attributes.email_ccStr);\n        if (message.request.attributes.email_ccStr != undefined) {\n          ccString = message.request.attributes.email_ccStr;\n          winston.debug(\"email set ccString\");\n        }\n      }\n    }\n    winston.debug(\"email inReplyTo: \" + inReplyTo);\n    winston.debug(\"email references: \" + references);\n    winston.debug(\"email cc: \", cc);\n    winston.debug(\"email ccString: \" + ccString);\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n\n    let subject = that.formatText(\"newMessageFollowerSubject\", `${message.request ? message.request.ticket_id : '-'}`, message, project.settings);\n\n\n\n    that.send({\n      messageId: messageId,\n      // sender: message.senderFullname, //must be an email\n      from: from,\n      to: to,\n      cc: ccString,\n      replyTo: replyTo,\n      inReplyTo: inReplyTo,\n      references: references,\n      // subject:`${message.request ? message.request.subject : '-'}`, \n      subject: subject,  //gmail uses subject\n      text: html,\n      html: html,\n      config: configEmail,\n      headers: headers\n    });\n\n    // // messageId =  \"notification\" + messageId;\n\n    // // that.send({\n    // //   messageId: messageId,\n    // //   // sender: message.senderFullname, //must be an email\n    // //   to: that.bcc, \n    // //   replyTo: replyTo, \n    // //   inReplyTo: inReplyTo,\n    // //   references: references,\n    // //   // subject: `${message.request ? message.request.subject : '-'} - notification`, \n    // //   subject: `${message.request ? message.request.subject : '-'} - notification`, \n    // //   text:html, \n    // //   html:html,\n    // //   headers:headers\n    // // });\n\n\n  }\n\n\n  /*\n    sendEmailChannelTakingNotification(to, request, project, tokenQueryString) {\n  \n      var that = this;\n  \n      this.readTemplateFile('ticket-taking.txt', function(err, html) {\n        // this.readTemplateFile('ticket.html', function(err, html) {\n  \n  \n        var envTemplate = process.env.EMAIL_TICKET_HTML_TEMPLATE;\n         winston.debug(\"envTemplate: \" + envTemplate);\n  \n        if (envTemplate) {\n            html = envTemplate;\n        }\n  \n        winston.debug(\"html: \" + html);\n  \n        var template = handlebars.compile(html);\n  \n        var baseScope = JSON.parse(JSON.stringify(that));\n        delete baseScope.pass;\n  \n        var replacements = {        \n          request: request,\n          project: project.toJSON(),\n          tokenQueryString: tokenQueryString,\n          baseScope: baseScope    \n        };\n  \n        var html = template(replacements);\n        winston.debug(\"html: \" + html);\n  \n  \n        // if (message.request && message.request.lead && message.request.lead.email) {\n        //   winston.info(\"message.request.lead.email: \" + message.request.lead.email);\n        //   replyTo = replyTo + \", \"+ request.lead.email;\n        // }\n        \n  \n        that.send({to:to, replyTo: replyTo, subject:`R: ${request ? request.subject : '-'}`, text:html }); //html:html\n        that.send({to: that.bcc, replyTo: replyTo, subject: `R: ${request ? request.subject : '-'} - notification`, text:html});//html:html\n  \n      });\n    }\n  */\n\n\n\n\n\n\n\n  async sendEmailDirect(to, text, project, request_id, subject, tokenQueryString, sourcePage, payload, replyTo, quoteManager) {\n\n    var that = this;\n\n\n    if (project.toJSON) {\n      project = project.toJSON();\n    }\n\n    var html = await this.readTemplate('emailDirect.html', project.settings, \"EMAIL_DIRECT_HTML_TEMPLATE\");\n\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    let msgText = text;\n    if (this.markdown) {\n      msgText = marked(msgText);\n      msgText = DOMPurify.sanitize(msgText);\n    } else {\n      msgText = encode(msgText);\n    }\n\n\n    winston.debug(\"msgText: \" + msgText);\n    winston.debug(\"baseScope: \" + JSON.stringify(baseScope));\n\n\n    var replacements = {\n      project: project,\n      request_id: request_id,\n      seamlessPage: sourcePage,\n      msgText: msgText,\n      tokenQueryString: tokenQueryString,\n      baseScope: baseScope,\n      payload: payload\n    };\n\n    var html = template(replacements);\n    winston.debug(\"html: \" + html);\n\n\n    // let replyTo;\n\n    if (!replyTo && this.replyEnabled && request_id) {\n      replyTo = request_id + this.inboundDomainDomainWithAt;\n    }\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n    let subjectParsed = that.parseText(subject, payload);\n\n    // if (message.request && message.request.lead && message.request.lead.email) {\n    //   winston.info(\"message.request.lead.email: \" + message.request.lead.email);\n    //   replyTo = replyTo + \", \"+ message.request.lead.email;\n    // }\n\n    // if (!subject) {\n    //   subject = \"Tiledesk\"\n    // }\n\n    let email_enabled = true;\n\n    that.send({\n      from: from,\n      to: to,\n      replyTo: replyTo,\n      subject: subjectParsed,\n      text: html,\n      html: html,\n      config: configEmail,\n    }, email_enabled, project, quoteManager);\n\n  }\n\n\n\n  // ok\n  async sendPasswordResetRequestEmail(to, resetPswRequestId, userFirstname, userLastname) {\n\n    var that = this;\n\n    var html = await this.readTemplate('resetPassword.html', undefined, \"EMAIL_RESET_PASSWORD_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      resetPswRequestId: resetPswRequestId,\n      userFirstname: userFirstname,\n      userLastname: userLastname,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n\n    that.send({ to: to, subject: `[${this.brand_name}] Password reset request`, html: html });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] Password reset request - notification`, html: html });\n\n  }\n\n  // ok\n  async sendYourPswHasBeenChangedEmail(to, userFirstname, userLastname) {\n\n    var that = this;\n\n    var html = await this.readTemplate('passwordChanged.html', undefined, \"EMAIL_PASSWORD_CHANGED_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      userFirstname: userFirstname,\n      userLastname: userLastname,\n      to: to,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n\n    that.send({ to: to, subject: `[${this.brand_name}] Your password has been changed`, html: html });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] Your password has been changed - notification`, html: html });\n\n  }\n\n\n  // ok\n\n\n  /**\n   *! *** EMAIL: YOU HAVE BEEN INVITED AT THE PROJECT  ***\n   */\n  async sendYouHaveBeenInvited(to, currentUserFirstname, currentUserLastname, projectName, id_project, invitedUserFirstname, invitedUserLastname, invitedUserRole) {\n\n    var that = this;\n\n    var html = await this.readTemplate('beenInvitedExistingUser.html', undefined, \"EMAIL_EXUSER_INVITED_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      currentUserFirstname: currentUserFirstname,\n      currentUserLastname: currentUserLastname,\n      projectName: projectName,\n      id_project: id_project,\n      invitedUserFirstname: invitedUserFirstname,\n      invitedUserLastname: invitedUserLastname,\n      invitedUserRole: invitedUserRole,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n\n    that.send({ to: to, subject: `[${this.brand_name}] You have been invited to the '${projectName}' project`, html: html });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] You have been invited to the '${projectName}' project - notification`, html: html });\n  }\n\n  // ok\n\n\n  /**\n   *! *** EMAIL: YOU HAVE BEEN INVITED AT THE PROJECT (USER NOT REGISTERED) ***\n   */\n  async sendInvitationEmail_UserNotRegistered(to, currentUserFirstname, currentUserLastname, projectName, id_project, invitedUserRole, pendinginvitationid) {\n\n\n    var that = this;\n\n    var html = await this.readTemplate('beenInvitedNewUser.html', undefined, \"EMAIL_NEWUSER_INVITED_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      currentUserFirstname: currentUserFirstname,\n      currentUserLastname: currentUserLastname,\n      projectName: projectName,\n      id_project: id_project,\n      invitedUserRole: invitedUserRole,\n      pendinginvitationid: pendinginvitationid,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n    that.send({ to: to, subject: `[${this.brand_name}] You have been invited to the '${projectName}' project`, html: html });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] You have been invited to the '${projectName}' project - notification`, html: html });\n\n  }\n\n  // ok\n  async sendVerifyEmailAddress(to, savedUser, code) {\n\n\n    var that = this;\n\n    if (savedUser.toJSON) {\n      savedUser = savedUser.toJSON();\n    }\n    var html = await this.readTemplate('verify.html', undefined, \"EMAIL_VERIFY_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      savedUser: savedUser,\n      baseScope: baseScope,\n      code: code\n    };\n\n    var html = template(replacements);\n\n\n    that.send({ to: to, subject: `[${this.brand_name}] Verify your email address`, html: html });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] Verify your email address ` + to + \" - notification\", html: html });\n\n  }\n\n\n\n\n\n\n\n  // ok\n\n  async sendRequestTranscript(to, messages, request, project) {\n    winston.debug(\"sendRequestTranscript: \" + to);\n\n    //if the request came from rabbit mq?\n    if (request.toJSON) {\n      request = request.toJSON();\n    }\n\n    // if (project.toJSON) {\n    //   project = project.toJSON();\n    // }\n\n    var transcriptAsHtml = \"\"; //https://handlebarsjs.com/guide/expressions.html#html-escaping\n    messages.forEach(message => {\n      transcriptAsHtml = transcriptAsHtml + '[' + message.createdAt.toLocaleTimeString('en', { timeZone: 'UTC' }) + '] ' + message.senderFullname + ': ' + message.text + '<br>';\n    });\n    winston.debug(\"transcriptAsHtml: \" + transcriptAsHtml);\n\n\n\n    var that = this;\n\n    var html = await this.readTemplate('sendTranscript.html', project.settings, \"EMAIL_SEND_TRANSCRIPT_HTML_TEMPLATE\");\n\n    winston.debug(\"html: \" + html);\n\n    var template = handlebars.compile(html);\n\n    var baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n\n    var replacements = {\n      messages: messages,\n      request: request,\n      formattedCreatedAt: request.createdAt.toLocaleString('en', { timeZone: 'UTC' }),\n      transcriptAsHtml: transcriptAsHtml,\n      baseScope: baseScope\n    };\n\n    var html = template(replacements);\n\n\n\n    let from;\n    let configEmail;\n    if (project && project.settings && project.settings.email) {\n      if (project.settings.email.config) {\n        configEmail = project.settings.email.config;\n        winston.debug(\"custom email configEmail setting found: \", configEmail);\n      }\n      if (project.settings.email.from) {\n        from = project.settings.email.from;\n        winston.debug(\"custom from email setting found: \" + from);\n      }\n    }\n\n\n\n    //custom ocf here\n    // console.log(\"ocf\",project._id);\n    let subject = that.formatText(\"sendTranscriptSubject\", `[${this.brand_name}] Transcript`, request, project.settings);\n\n    //prod                                               //pre\n    // if (project._id ==\"6406e34727b57500120b1bd6\" || project._id == \"642c609f179910002cc56b3e\") {\n    //   subject = \"Segnalazione #\" + request.ticket_id;\n    //   // subject = \"Richiesta di supporto #\" + request.ticket_id;\n    //   if (request.subject) {\n    //     subject = subject + \" - \" + request.subject;\n    //   } \n    //   // console.log(\"subject\",subject);\n    // }\n    // hcustomization.emailTranscript(to, subject, html, configEmail)\n\n    that.send({ from: from, to: to, subject: subject, html: html, config: configEmail });\n    that.send({ to: that.bcc, subject: `[${this.brand_name}] Transcript - notification`, html: html });\n\n  }\n\n  async sendEmailRedirectOnDesktop(to, token, project_id, chatbot_id, namespace_id) {\n    winston.debug(\"sendEmailRedirectOnDesktop: \" + to);\n\n    var that = this;\n\n    let html = await this.readTemplate('redirectToDesktopEmail.html', undefined, \"EMAIL_REDIRECT_TO_DESKTOP_TEMPLATE\");\n\n\n    winston.debug(\"html: \" + html);\n\n    let template = handlebars.compile(html);\n\n    let baseScope = JSON.parse(JSON.stringify(that));\n    delete baseScope.pass;\n\n    let redirect_url;\n    if (chatbot_id) {\n      redirect_url = `https://panel.tiledesk.com/v3/cds/#/project/${project_id}/chatbot/${chatbot_id}/intent/0?jwt=${token}`;\n    } else {\n      redirect_url = `${baseScope.baseUrl}/#/project/${project_id}/knowledge-bases/${namespace_id}?token=${token}`;\n    }\n\n    let replacements = {\n      baseScope: baseScope,\n      redirect_url: redirect_url,\n      token: token,\n      project_id: project_id,\n      chatbot_id: chatbot_id\n    }\n\n    html = template(replacements);\n\n    that.send({ to: to, subject: `Join ${this.brand_name} from Desktop`, html: html });\n\n  }\n\n  async sendEmailQuotaCheckpointReached(to, firstname, project_name, resource_name, checkpoint, quotes) {\n\n    winston.info(\"sendEmailQuotaCheckpointReached: \" + to);\n    \n    var that = this;\n\n    let html = await this.readTemplate('checkpointReachedEmail.html', undefined, \"EMAIL_QUOTA_CHECKPOINT_REACHED_TEMPLATE\");\n    winston.debug(\"html: \" + html);\n\n    let template = handlebars.compile(html);\n\n    let requests_quote = quotes.requests.quote;\n    let requests_perc = quotes.requests.perc;\n\n    let tokens_quote = quotes.tokens.quote;\n    let tokens_perc = quotes.tokens.perc;\n\n    let email_quote = quotes.email.quote;\n    let email_perc = quotes.email.perc;\n\n    let replacements = {\n      firstname: firstname,\n      project_name: project_name,\n      resource_name: resource_name,\n      checkpoint: checkpoint,\n      requests_quote: requests_quote,\n      requests_perc: requests_perc,\n      tokens_quote: tokens_quote,\n      tokens_perc: tokens_perc,\n      email_quote: email_quote,\n      email_perc: email_perc\n    }\n\n    html = template(replacements);\n\n    that.send({ to: to, subject: \"Update on resources usage\", html: html });\n  }\n\n  parseText(text, payload) {\n\n\n\n    var baseScope = JSON.parse(JSON.stringify(this));\n    delete baseScope.pass;\n\n    winston.debug(\"parseText text: \" + text);\n\n    var templateHand = handlebars.compile(text);\n\n    var replacements = {\n      payload: payload,\n      baseScope: baseScope,\n      test: \"test\"\n    };\n\n    var textTemplate = templateHand(replacements);\n    winston.debug(\"parseText textTemplate: \" + textTemplate);\n\n    return textTemplate;\n\n\n  }\n\n  formatText(templateName, defaultText, payload, settings) {\n\n    let text = defaultText;\n    winston.debug(\"formatText defaultText: \" + defaultText);\n\n    let template = this.getTemplate(templateName, settings);\n\n    winston.debug(\"formatText template: \" + template);\n\n    if (template) {\n      text = template;\n    }\n\n    var baseScope = JSON.parse(JSON.stringify(this));\n    delete baseScope.pass;\n\n    winston.debug(\"formatText text: \" + text);\n\n    var templateHand = handlebars.compile(text);\n\n    var replacements = {\n      payload: payload,\n      baseScope: baseScope,\n      test: \"test\"\n    };\n\n    var textTemplate = templateHand(replacements);\n    winston.debug(\"formatText textTemplate: \" + textTemplate);\n\n    return textTemplate;\n\n  }\n\n  getTemplate(templateName, settings) {\n\n    var that = this;\n    winston.debug('getTemplate formatSubject: ' + JSON.stringify(settings));\n\n\n    if (settings && settings.email && settings.email.templates) {\n      winston.debug('getTemplate settings.email.templates: ', settings.email.templates);\n\n      var templates = settings.email.templates;\n      winston.debug('getTemplate templates: ', templates);\n\n      var templateDbName = templateName.replace(\".subject\", \"\");\n      winston.debug('getTemplate templateDbName: ' + templateDbName);\n\n\n      var template = templates[templateDbName];\n      winston.debug('getTemplate template: ' + template);\n\n      if (template) {\n        // that.callback(template);\n        // return new Promise(function (resolve, reject) {\n        // return resolve(template);\n        return template;\n        // });\n      } else {\n        return undefined;\n      }\n    } else {\n      return undefined;\n    }\n\n  }\n\n\n\n}\n\n\nvar emailService = new EmailService();\n\n// var subject = \"abc\";\n// hcustomization.emailTranscript({subject: subject});\n// console.log(\"subject\", subject);\n\nmodule.exports = emailService;\n"
  },
  {
    "path": "services/faqBotHandler.js",
    "content": "\nconst botEvent = require('../event/botEvent');\nvar Faq = require('../models/faq');\nvar Faq_kb = require('../models/faq_kb');\nvar messageService = require('../services/messageService');\nvar MessageConstants = require(\"../models/messageConstants\");\nvar winston = require('../config/winston');\nvar faqBotSupport = require('../services/faqBotSupport');\nvar BotFromParticipant = require(\"../utils/botFromParticipant\");\nvar cacheUtil = require('../utils/cacheUtil');\nvar eventService = require('../pubmodules/events/eventService');\nvar mongoose = require('mongoose');\nconst ActionsConstants = require('../models/actionsConstants');\nvar httpUtil = require('../utils/httpUtil');\n\nvar webhook_origin = process.env.WEBHOOK_ORIGIN || \"http://localhost:3000\";\nwinston.debug(\"webhook_origin: \"+webhook_origin);\n\nclass FaqBotHandler {\n \n    static is_command(text) {\n        // console.log(\"msg:\", msg);\n        if (!text) {\n          return {\n            'command': null,\n            'text': null\n          }\n        }\n        // const text = msg.text;\n        // console.log(\"msg.text:\", msg.text);\n        // console.log(\"TiledeskChatbotUtil.AGENT_COMMAND:\", TiledeskChatbotUtil.AGENT_COMMAND.replace(/\\\\\\\\/g, '\\\\'));\n        //const agent_pattern = new RegExp('^(' + TiledeskChatbotUtil.AGENT_COMMAND.replace(/\\\\/g, '\\\\\\\\') + ')$', 'm');\n        // console.log(\"agent_pattern:\", agent_pattern);\n        //const match_agent = text.match(agent_pattern);\n        //console.log(\"match_agent: \", match_agent);\n        const match_agent = text.indexOf(ActionsConstants.CHAT_ACTION_MESSAGE.AGENT);\n        const match_close = text.indexOf(ActionsConstants.CHAT_ACTION_MESSAGE.CLOSE);\n        //console.log(\"match_agent: \", match_agent);\n        // const agent_handoff = null;\n        //if (match_agent && match_agent.length >=2) {\n        if (match_close >-1) {\n            // console.log(\"match!\");\n            //   let parts = text.split('\\\\agent');\n            // console.log(parts)\n            const new_msg_text = text.replace(ActionsConstants.CHAT_ACTION_MESSAGE.CLOSE,\"\");\n            //   const new_msg_text = parts[0].trim()\n            // console.log(new_msg_text)\n            return {\n                'command': ActionsConstants.CHAT_ACTION_MESSAGE.CLOSE,\n                'text': new_msg_text\n            }\n        }\n        if (match_agent >-1) {\n          // console.log(\"match!\");\n        //   let parts = text.split('\\\\agent');\n          // console.log(parts)\n          const new_msg_text = text.replace(ActionsConstants.CHAT_ACTION_MESSAGE.AGENT,\"\");\n        //   const new_msg_text = parts[0].trim()\n          // console.log(new_msg_text)\n          return {\n            'command': ActionsConstants.CHAT_ACTION_MESSAGE.AGENT,\n            'text': new_msg_text\n          }\n        }\n        return {\n          'command': null,\n          'text': text\n        }\n      }\n\n      \n    listen() {\n\n        var that = this;\n        //modify to async\n        botEvent.on('bot.message.received.notify.internal', function(message) {\n                           \n\n        //    var botName = message.request.department.bot.name;\n        //    winston.debug(\"botName \" + botName);\n\n           var botId =  BotFromParticipant.getBotId(message);\n\n           winston.debug(\"botId \" + botId);\n\n           winston.debug(\"message.text \"+ message.text);\n         \n\n           Faq_kb.findById(botId)  //TODO add cache_bot_NOT_here it's internal bot that is deprecated-> skip caching\n           //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, message.id_project+\":faq_kbs:id:\"+botId)\n           .exec(function(err, faq_kb) {\n            if (err) {\n              return winston.error(\"Error getting bot object.\",err);\n            }\n            if (!faq_kb) {\n                return winston.error(\"Bot not found with id: \"+botId);              \n            }\n            winston.debug('faq_kb ', faq_kb.toJSON());\n            winston.debug('faq_kb.type :'+ faq_kb.type);\n\n            var botName = faq_kb.name;\n            winston.debug(\"botName \" + botName);\n           \n\n            var query = { \"id_project\": message.id_project, \"id_faq_kb\": faq_kb._id, \"question\": message.text};\n\n            if (message.attributes && message.attributes.action) {\n\n                var action = message.attributes.action;\n                var action_parameters_index = action.indexOf(\"?\");\n                if (action_parameters_index > -1) {\n                    action = action.substring(0,action_parameters_index);\n                }\n                winston.debug(\"action: \" + action);\n\n                var isObjectId = mongoose.Types.ObjectId.isValid(action);\n                winston.debug(\"isObjectId:\"+ isObjectId);\n                             \n              \n                if (isObjectId) {                    \n                    query = { \"id_project\": message.id_project, \"id_faq_kb\": faq_kb._id, \"_id\": action};\n                }else {\n                    query = { \"id_project\": message.id_project, \"id_faq_kb\": faq_kb._id,  $or:[{\"intent_id\": action}, {\"intent_display_name\": action}]};\n                }\n                \n                winston.debug(\"query message.attributes.action \", query);\n            }\n\n            \n            if (message.request && message.request.attributes && message.request.attributes.blocked_intent) {\n                query = { \"id_project\": message.id_project, \"id_faq_kb\": faq_kb._id,  $or:[{\"intent_id\": message.request.attributes.blocked_intent}, {\"intent_display_name\": message.request.attributes.blocked_intent}]};\n                // TODO skip if type  reset (better create a resetIntent action /reset reset the attributes) -> TODO DELETE message.request.attributes.blocked_intent for next call\n                winston.debug(\"query message.attributes.blocked_intent \", query);\n                // res.send({text:\"ripeti la mail\", action: id_intent}); fai un test che forse già funziona\n            }\n            \n\n           \n\n            // EXACT MATCH\n            Faq.find(query) \n            .lean().               \n             exec(async (err, faqs) => {\n               if (err) {\n                return winston.error(\"Error getting faq object.\",err);                \n               }\n\n               if (faqs && faqs.length>0) {\n                    winston.debug(\"faqs exact\", faqs);              \n        \n                    winston.debug(\"faqs\", faqs);              \n\n                    // botprefix\n                    let sender = 'bot_' + botId;\n                    winston.debug(\"sender\", sender);          \n                \n\n                    var answerObj;\n                    if (faqs && faqs.length>0 && faqs[0].answer) {\n                        answerObj = faqs[0];     \n\n                        answerObj.score = 100; //exact search not set score\n                        winston.debug(\"answerObj.score\", answerObj.score);  \n\n                        // qui\n                        faqBotSupport.getParsedMessage(answerObj.answer, message, faq_kb, answerObj).then(function(bot_answer) {\n                        // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type) {\n                           \n\n                            var attr = bot_answer.attributes;                            \n                            if (!attr) {\n                                attr = {};\n                            }\n                           // attr._answer = that.getCircularReplacer(answerObj);\n                            if (answerObj && answerObj._id) {\n                                attr._answerid = answerObj._id.toString();\n                            }                            \n                            \n                            let question_payload = Object.assign({}, message);\n                            delete question_payload.request;\n\n                            winston.debug(\"question_payload\", question_payload);\n\n                            let clonedfaqs = faqs.slice();\n                            if (clonedfaqs && clonedfaqs.length>0) {\n                                clonedfaqs = clonedfaqs.shift()\n                            }\n                            winston.debug(\"clonedfaqs\", clonedfaqs);\n\n                            const intent_info = {\n                                intent_name: answerObj.intent_display_name,\n                                is_fallback: false,\n                                confidence: answerObj.score,\n                                question_payload: question_payload,\n                                others: clonedfaqs\n                              }\n                            winston.debug(\"intent_info\", intent_info);\n                            attr.intent_info = intent_info;\n                                                       \n                            winston.debug(\"answerObj\", answerObj);\n                            // winston.info(\"that.getCircularReplacer(answerObj)\",  that.getCircularReplacer(answerObj));\n                            winston.debug(\"attr\", attr);                            \n\n\n\n                            const command_parsed = FaqBotHandler.is_command(bot_answer.text);\n                            winston.debug('command_parsed?', command_parsed);\n    \n                            if (command_parsed.command) {\n                                winston.debug(\"agent_handoff faqs command found\");                    \n                            \n                                messageService.send(sender, botName, message.recipient, command_parsed.command, \n                                    message.id_project, sender, {subtype: \"info\"}, 'text', undefined).then(function(savedMessage){\n                                        winston.debug(\"agent_handoff faqs agent sent \", savedMessage.toObject());  \n                                }).catch(function(err){    \n                                    winston.log({\n                                      level: 'error',\n                                      message: 'Error sending message bot: '+ JSON.stringify(err) ,\n                                      label: message.id_project\n                                    });\n                                });                                                                \n                                 // PATCH: Chat clients (i.e. web widget) remove messages with text = null\n                                // command_parsed.text contains the eventual text before the \\agent command\n                                // or 'all the message text' if \\agent was not found\n                                bot_answer.text = command_parsed.text? command_parsed.text : undefined;\n                                winston.debug(\"bot_answer.text1 \"+ bot_answer.text );\n                            } \n\n\n\n                            winston.debug(\"bot_answer.text2 \"+ bot_answer.text );\n                            // if (bot_answer.text) { //can be undefined id /agent only\n                                messageService.send(sender, botName, message.recipient, bot_answer.text, \n                                    message.id_project, sender, attr, bot_answer.type, bot_answer.metadata, bot_answer.language).then(function(savedMessage){\n                                        winston.debug(\"faqbot message botAns \", savedMessage.toObject());  \n                                }).catch(function(err){    \n                                    winston.log({\n                                      level: 'error',\n                                      message: 'Error sending message bot: '+ JSON.stringify(err) ,\n                                      label: message.id_project\n                                    });\n                                });                                    \n                            // }\n                            \n                                                    \n                           \n                        });\n                      \n        \n                    }\n                    \n                        \n\n               } else {\n \n\n                query = { \"id_project\": message.id_project, \"id_faq_kb\": faq_kb._id};\n                var mongoproject = undefined;\n                var sort = undefined;\n\n                //make http request external   \n                if (faq_kb.url) {\n\n                                                                                        \n                    var url = faq_kb.url+\"/parse\";\n                    winston.debug(\"fulltext search external url \" + url);   \n\n                    var json = {text: message.text, language: faq_kb.language, id_project: message.id_project, id_faq_kb: faq_kb._id};\n                    winston.debug(\"fulltext search external json\", json);   \n\n                    var headers = {\n                        'Content-Type' : 'application/json', \n                        'User-Agent': 'tiledesk-bot',\n                        'Origin': webhook_origin\n                        };\n\n                    var res = await httpUtil.call(url, headers, json, \"POST\")\n                    winston.debug(\"res\", res);\n                    \n                    if (res && res.intent && res.intent.name) {\n                        var intent_name = res.intent.name;\n                        winston.debug(\"intent_name\", intent_name);\n                        //filtra su intent name\n                        query.intent_display_name = intent_name;\n                        winston.debug(\"query\",query);                        \n                    \n                    }\n                } else {\n\n                    var search_obj = {\"$search\": message.text};\n\n                    if (faq_kb.language) {\n                        search_obj[\"$language\"] = faq_kb.language;\n                    }\n                    query.$text = search_obj;\n                    winston.debug(\"fulltext search query\", query);   \n                    \n                    mongoproject = {score: { $meta: \"textScore\" } };\n                    sort = { score: { $meta: \"textScore\" } } //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n                }               \n        \n            \n                \n                Faq.find(query,  mongoproject)  \n                .sort(sort) \n                .lean().               \n                exec(async (err, faqs) => {\n                    if (err) {\n                        return winston.error('Error getting fulltext objects.', err);      \n                    }\n                    winston.debug(\"faqs\", faqs);              \n\n                    // botprefix\n                    let sender = 'bot_' + botId;\n                    winston.debug(\"sender\", sender);          \n                \n\n                    var answerObj;\n                    if (faqs && faqs.length>0 && faqs[0].answer) {\n                        answerObj = faqs[0];                \n\n\n                        // non fare la ricerca fulltext \n                        //make http request external   \n                        /*\n                        if (faq_kb.url) {\n\n                                                                                                \n                            var url = faq_kb.url+\"/parse\";\n                            winston.verbose(\"fulltext search external url \" + url);   \n\n                            var json = {text: message.text, language: faq_kb.language, id_project: message.id_project, id_faq_kb: faq_kb._id};\n                            winston.verbose(\"fulltext search external json\", json);   \n\n                            var headers = {\n                                'Content-Type' : 'application/json', \n                                'User-Agent': 'tiledesk-bot',\n                                'Origin': webhook_origin\n                                };\n\n                            var res = await httpUtil.call(url, headers, json, \"POST\")\n                            console.log(\"res\", res);\n                            \n                            if (res && res.intent && res.intent.name) {\n                                var intent_name = res.intent.name;\n                                console.log(\"intent_name\", intent_name);\n                                                        //filtra su intent name\n                                var queryExternal = { id_project: message.id_project, id_faq_kb: faq_kb._id, intent_display_name: intent_name};\n                                winston.verbose(\"queryExternal\",queryExternal);\n\n                                var faqExternal = await Faq.findOne(queryExternal) \n                                    .lean().               \n                                    exec();\n\n                                winston.verbose(\"faqExternal\",faqExternal);\n\n                                if (faqExternal) {\n                                    answerObj = faqExternal;\n                                }\n                            \n                            }\n                        }\n                        */\n\n\n\n\n\n                        // qui\n                        faqBotSupport.getParsedMessage(answerObj.answer, message, faq_kb, answerObj).then(function(bot_answer) {\n\n                            var attr = bot_answer.attributes;                            \n                            if (!attr) {\n                                attr = {};\n                            }\n                            // attr._answer = that.getCircularReplacer(answerObj);\n                            if (answerObj && answerObj._id) {\n                                attr._answerid = answerObj._id.toString();\n                            }\n\n\n\n                            let question_payload = Object.assign({}, message);\n                            delete question_payload.request;\n\n                            winston.debug(\"question_payload\", question_payload);\n\n                            let clonedfaqs = faqs.slice();\n                            if (clonedfaqs && clonedfaqs.length>0) {\n                                clonedfaqs = clonedfaqs.shift()\n                            }\n                            winston.debug(\"clonedfaqs\", clonedfaqs);\n\n                            const intent_info = {\n                                intent_name: answerObj.intent_display_name,\n                                is_fallback: false,\n                                confidence: answerObj.score,\n                                question_payload: question_payload,\n                                others: clonedfaqs\n                              }\n                            winston.debug(\"intent_info\", intent_info);\n                            attr.intent_info = intent_info;\n\n\n                            \n                            winston.debug(\"attr\", attr);\n\n\n                            const command_parsed = FaqBotHandler.is_command(bot_answer.text);\n                            winston.debug('command_parsed?', command_parsed);\n    \n                            if (command_parsed.command) {\n                                winston.debug(\"agent_handoff faqs command found\");                    \n                            \n                                messageService.send(sender, botName, message.recipient, command_parsed.command, \n                                    message.id_project, sender, {subtype: \"info\"}, 'text', undefined).then(function(savedMessage){\n                                        winston.debug(\"agent_handoff faqs agent sent \", savedMessage.toObject());  \n                                }).catch(function(err){    \n                                    winston.log({\n                                      level: 'error',\n                                      message: 'Error sending message bot: '+ JSON.stringify(err) ,\n                                      label: message.id_project\n                                    });\n                                });           \n\n                                 // PATCH: Chat clients (i.e. web widget) remove messages with text = null\n                                // command_parsed.text contains the eventual text before the \\agent command\n                                // or 'all the message text' if \\agent was not found\n                                bot_answer.text = command_parsed.text? command_parsed.text : undefined;\n                                winston.debug(\"bot_answer.text1 \"+ bot_answer.text );\n                            } \n\n\n\n                            // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes) {\n                                messageService.send(sender, botName, message.recipient, bot_answer.text, \n                                    message.id_project, sender, attr, bot_answer.type, bot_answer.metadata, bot_answer.language).then(function(savedMessage){\n\n                                        winston.debug(\"faqbot message sending \", savedMessage.toObject());  \n                                }).catch(function(err){    \n                                    winston.log({\n                                      level: 'error',\n                                      message: 'Error sending message bot: '+ JSON.stringify(err) ,\n                                      label: message.id_project\n                                    });\n                                });           \n                        });\n                        \n        \n                    }\n                    \n                        var threshold = 1.2;\n\n                        faqBotSupport.getBotMessage(answerObj, message.id_project, faq_kb, message, threshold).then(function(botAns){\n                        winston.debug(\"faqbot message botAns \", botAns);  \n\n                        if (botAns) {\n\n                            if (botAns.defaultFallback === true) {\n                                winston.debug(\"defaultFallback event\");  \n                                var project_user = undefined;\n                                // emit(name, attributes, id_project, project_user, createdBy, status) {                                \n                                eventService.emit(\"faqbot.answer_not_found\", \n                                          // optional. TODO fare solo text\n                                        { botAnswer:answerObj, \n                                            bot: faq_kb, message:message},\n                                        //threshold:threshold}, \n                                        message.id_project, project_user,  \"system\", undefined);\n                            }\n                           \n\n                            var attr = botAns.attributes;                            \n                            if (!attr) {\n                                attr = {};\n                            }\n\n                     \n                            winston.debug(\"botAns\", botAns);\n\n\n                            //Mongoose error to save botAns\n                            // let clonedBotAns = Object.assign({}, botAns);\n                            // delete clonedBotAns.attributes;\n                            // winston.info(\"********clonedBotAns: \"+ JSON.stringify(clonedBotAns));\n\n                            // attr._answer = clonedBotAns;\n                            if (botAns._id) {\n                                attr._answerid = botAns._id.toString();\n                            }\n                            \n\n\n                            let question_payload = Object.assign({}, message);\n                            delete question_payload.request;\n\n                            winston.debug(\"question_payload\", question_payload);\n\n                            const intent_info = {\n                                intent_name: \"DEFAULT_FALLBACK\", //answerObj.intent_display_name\n                                is_fallback: true,\n                                confidence: 0,\n                                question_payload: question_payload \n                              }\n                            winston.debug(\"intent_info\", intent_info);\n                            attr.intent_info = intent_info;\n\n                            \n\n\n                            winston.debug(\"attr\", attr);\n\n\n\n                            const command_parsed = FaqBotHandler.is_command(botAns.text);\n                            winston.debug('command_parsed?', command_parsed);\n    \n                            if (command_parsed.command) {\n                                winston.debug(\"agent_handoff faqs command found\");                    \n                            \n                                messageService.send(sender, botName, message.recipient, command_parsed.command, \n                                    message.id_project, sender, {subtype: \"info\"}, 'text', undefined).then(function(savedMessage){\n                                        winston.debug(\"agent_handoff faqs agent sent \", savedMessage.toObject());  \n                                }).catch(function(err){    \n                                    winston.log({\n                                      level: 'error',\n                                      message: 'Error sending message bot: '+ JSON.stringify(err) ,\n                                      label: message.id_project\n                                    });\n                                });                                                      \n                                 // PATCH: Chat clients (i.e. web widget) remove messages with text = null\n                                // command_parsed.text contains the eventual text before the \\agent command\n                                // or 'all the message text' if \\agent was not found\n                                botAns.text = command_parsed.text? command_parsed.text : undefined;\n                                winston.debug(\"bot_answer.text1 \"+ botAns.text );\n                            } \n\n\n\n\n                            // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata) \n                            messageService.send(sender, botName, message.recipient, botAns.text, \n                                message.id_project, sender, attr, botAns.type, botAns.metadata, botAns.language).then(function(savedMessage){\n                                    winston.debug(\"faqbot message botAns \" ,savedMessage.toObject());  \n                            })\n                            .catch(function(err){    \n                                winston.log({\n                                  level: 'error',\n                                  message: 'Error sending message bot: '+ JSON.stringify(err) + \" \" + JSON.stringify(botAns.text) ,\n                                  label: message.id_project\n                                });\n                            });\n                        }\n\n                    });\n\n\n               \n\n        \n                });\n\n            }\n           \n                \n\n        });\n\n\n\n             });\n\n          \n        });\n    }\n\n\n\n\n\n\n\n    \n   \n\n    \n}\n\nvar faqBotHandler = new FaqBotHandler();\nmodule.exports = faqBotHandler;"
  },
  {
    "path": "services/faqBotSupport.js",
    "content": "\n\n'use strict';\n\nconst Faq = require('../models/faq');\nconst Faq_kb = require('../models/faq_kb');\nvar winston = require('../config/winston');\n\nvar jwt = require('jsonwebtoken');\nconst uuidv4 = require('uuid/v4');\n\nconst { TiledeskChatbotUtil } = require('@tiledesk/tiledesk-chatbot-util');\n\nvar request = require('retry-request', {\n    request: require('request')\n  });\n\n \nvar webhook_origin = process.env.WEBHOOK_ORIGIN || \"http://localhost:3000\";\nwinston.debug(\"webhook_origin: \"+webhook_origin);\n\nclass FaqBotSupport {\n\n\n      \n\n    getMessage(key, lang, labelsObject) {\n       \n\n        if (!lang) {\n            lang = \"EN\";\n        }\n\n        lang = lang.toUpperCase();\n\n        winston.debug('getMessage: ' + key + ' ' + lang+ ' ' + JSON.stringify(labelsObject) );\n\n        var label = \"\";\n\n        try {\n            // winston.debug(\"1\");\n         label = labelsObject[lang][key];\n        //  winston.debug(\"2\");\n        } catch(e) {\n            // winston.debug(\"Error\", e);\n         label = labelsObject[\"EN\"][key];\n        }\n        winston.debug('label: ' + label );\n        return label;\n     \n    }\n// usa api di sponziello parseReply: https://github.com/Tiledesk/tiledesk-nodejs-libs/blob/master/tiledesk-chatbot-util/index.js \n\n    parseMicrolanguage(text, message, bot, faq, disableWebHook, json) { \n        var that = this;\n        return new Promise(async (resolve, reject) => {\n            winston.debug('parseMicrolanguage message: ' + JSON.stringify(message) );\n            var reply = TiledeskChatbotUtil.parseReply(text);\n            winston.debug('parseReply: ' + JSON.stringify(reply) );\n            var messageReply = reply.message;\n            \n            var msg_attributes = {\"_raw_message\": text};\n\n            // prendi attributi e li mergi\n            // metadata prendi da messageReply SOLO SE CI SONO (DIVERSI NULL). Se riesci fai il merge\n            // prendi type e text\n\n            // if (message && message.attributes) {\n            //     for(const [key, value] of Object.entries(message.attributes)) {\n            //       msg_attributes[key] = value\n            //     }\n            // }\n\n            if (json && json.attributes) {\n                for(const [key, value] of Object.entries(json.attributes)) {\n                  msg_attributes[key] = value\n                }\n            }\n\n            if (messageReply && messageReply.attributes) {\n                for(const [key, value] of Object.entries(messageReply.attributes)) {\n                  msg_attributes[key] = value\n                }\n            }\n\n            messageReply.attributes = msg_attributes;\n            \n            // not used in faqBotHandler but used when the message is returned by webhook (subscription). So you must clone(add) all message fields here.\n            // winston.debug('message.language: '+ message.language );\n            // if (message.language) {\n            //     messageReply.language = message.language;\n            // }\n\n            if (json && json.language) {\n                messageReply.language = json.language;\n            }\n\n            if (json && json.type) {\n                messageReply.type = json.type;\n            } \n            \n            if (json && json.metadata) {\n                messageReply.metadata = json.metadata;\n            }\n\n        \n            winston.debug('faq: ', faq );\n            if (disableWebHook === false && bot.webhook_enabled ===true && (faq.webhook_enabled  === true || reply.webhook)) {\n\n                winston.debug(\"bot.webhook_url \"+ bot.webhook_url)\n                var webhookurl = bot.webhook_url;\n\n               \n                winston.debug(\"reply.webhook \"+ reply.webhook )\n\n                if (reply.webhook) {\n                     if (reply.webhook === true) {\n                        webhookurl = bot.webhook_url;\n                    } else {\n                        webhookurl = reply.webhook;\n                    }\n                }\n\n                if (!webhookurl) {\n                    winston.debug(\"webhookurl is undefined return standard\");\n                    return resolve(messageReply);\n                }\n                    \n                var botWithSecret = await Faq_kb.findById(bot._id).select('+secret').exec();  //TODO add cache_bot_nOT_here?? it's internal bot that is deprecated-> skip caching\n\n                var signOptions = {\n                    issuer:  'https://tiledesk.com',\n                    subject:  'bot',\n                    audience:  'https://tiledesk.com/bots/'+bot._id,   \n                    jwtid: uuidv4()       \n                    };\n            \n                    // TODO metti bot_? a user._id\n                var token = jwt.sign(bot.toObject(), botWithSecret.secret, signOptions);                  \n\n\n                winston.debug(\"webhookurl \"+ webhookurl)\n\n                return request({                        \n                    uri :  webhookurl,\n                    headers: {\n                        'Content-Type' : 'application/json', \n                        'User-Agent': 'tiledesk-bot',\n                        'Origin': webhook_origin\n                         //'x-hook-secret': s.secret\n                       },\n                    method: 'POST',\n                    json: true,\n                    body: {payload:{text: text, bot: bot, message: message, intent: faq}, token: token},\n                    // }).then(response => {\n                    }, function(err, response, json){\n                        if (err) {\n                            winston.error(\"Error from webhook reply of getParsedMessage. Return standard reply\", err);\n\n                            return resolve(messageReply);\n\n                            // return error\n                            /*\n                            var bot_answer = {};\n                            bot_answer.text = err.toString(); \n                            if(response && response.text) {\n                                bot_answer.text = bot_answer.text + ' '+response.text;\n                            }\n                            bot_answer.type = \"text\";\n                            \n                            return resolve(bot_answer);\n                            */\n                        }\n                         if (response.statusCode >= 400) {      \n                            winston.verbose(\"The ChatBot webhook return error http status code. Return standard reply\", response);            \n                            return resolve(messageReply);\n                        }\n\n                        if (!json) { //the webhook return empty body\n                            winston.verbose(\"The ChatBot webhook return no json. Return standard reply\", response);\n                            return resolve(messageReply);\n                        }\n                       \n                        winston.debug(\"webhookurl repl_message \", response);\n\n                        var text = undefined;\n                        if(json && json.text===undefined) {\n                            winston.verbose(\"webhookurl json is defined but text not. return standard reply\",{json:json, response:response});\n                            // text = 'Field text is not defined in the webhook respose of the faq with id: '+ faq._id+ \". Error: \" + JSON.stringify(response);\n                            return resolve(messageReply);\n                        }else {\n                            text = json.text;\n                        }\n                        winston.debug(\"webhookurl text:  \"+ text);\n\n                        // // let cloned_message = Object.assign({}, messageReply);\n                        // let cloned_message =  message;\n                        // winston.debug(\"cloned_message :  \",cloned_message);\n\n                        // if (json.attributes) {\n                        //     if (!cloned_message.attributes) {\n                        //         cloned_message.attributes = {}\n                        //     }\n                        //     winston.debug(\"ChatBot webhook json.attributes: \",json.attributes);\n                        //     for(const [key, value] of Object.entries(json.attributes)) {\n                        //         cloned_message.attributes[key] = value\n                        //     }\n                        // }\n\n                        // winston.debug(\"cloned_message after attributes:  \",cloned_message);\n\n                        that.parseMicrolanguage(text, message, bot, faq, true, json).then(function(bot_answer) {\n                            return resolve(bot_answer);\n                        });\n                    });\n            }\n\n            return resolve(messageReply);\n        });\n    }\n\n    getParsedMessage(text, message, bot, faq) { \n        return this.parseMicrolanguage(text, message, bot, faq, false);\n        // return this.parseMicrolanguageOld(text, message, bot, faq);\n    }\n\n    // parseMicrolanguageOld(text, message, bot, faq) { \n    //     var that = this;\n    //     // text = \"*\"\n    //     return new Promise(function(resolve, reject) {\n    //         winston.debug(\"getParsedMessage ******\",text);\n    //         var repl_message = {};\n\n    //         // cerca i bottoni eventualmente definiti\n    //         var button_pattern = /^\\*.*/mg; // buttons are defined as a line starting with an asterisk            \n    //         var text_buttons = text.match(button_pattern);\n    //         if (text_buttons) {\n    //             var text_with_removed_buttons = text.replace(button_pattern,\"\").trim();\n    //             repl_message.text = text_with_removed_buttons\n    //             var buttons = []\n    //             text_buttons.forEach(element => {\n    //             winston.debug(\"button \", element)\n    //             var remove_extra_from_button = /^\\*/mg;\n    //             var button_text = element.replace(remove_extra_from_button, \"\").trim()\n    //             var button = {}\n    //             button[\"type\"] = \"text\"\n    //             button[\"value\"] = button_text\n    //             buttons.push(button)\n    //             });\n    //             repl_message.attributes =\n    //             { \n    //             attachment: {\n    //                 type:\"template\",\n    //                 buttons: buttons\n    //             }\n    //             }\n    //             repl_message.type = MessageConstants.MESSAGE_TYPE.TEXT;\n    //         } else {\n    //             // no buttons\n    //             repl_message.text = text\n    //             repl_message.type =  MessageConstants.MESSAGE_TYPE.TEXT;\n    //         }\n\n    //         var image_pattern = /^\\\\image:.*/mg; \n    //         var imagetext = text.match(image_pattern);\n    //         if (imagetext && imagetext.length>0) {\n    //             var imageurl = imagetext[0].replace(\"\\\\image:\",\"\").trim();\n    //             winston.debug(\"imageurl \", imageurl)\n    //             var text_with_removed_image = text.replace(image_pattern,\"\").trim();\n    //             repl_message.text = text_with_removed_image + \" \" + imageurl\n    //             repl_message.metadata = {src: imageurl, width:200, height:200};\n    //             repl_message.type =  MessageConstants.MESSAGE_TYPE.IMAGE;\n    //         }\n\n    //         var frame_pattern = /^\\\\frame:.*/mg; \n    //         var frametext = text.match(frame_pattern);\n    //         if (frametext && frametext.length>0) {\n    //             var frameurl = frametext[0].replace(\"\\\\frame:\",\"\").trim();\n    //             winston.debug(\"frameurl \", frameurl)\n    //             // var text_with_removed_image = text.replace(frame_pattern,\"\").trim();\n    //             // repl_message.text = text_with_removed_image + \" \" + imageurl\n    //             repl_message.metadata = {src: frameurl};\n    //             repl_message.type = MessageConstants.MESSAGE_TYPE.FRAME;\n    //         }\n\n\n    //         var webhook_pattern = /^\\\\webhook:.*/mg; \n    //         var webhooktext = text.match(webhook_pattern);\n    //         if (webhooktext && webhooktext.length>0) {\n    //             var webhookurl = webhooktext[0].replace(\"\\\\webhook:\",\"\").trim();\n    //             winston.debug(\"webhookurl \", webhookurl)\n\n    //             return request({                        \n    //                 uri :  webhookurl,\n    //                 headers: {\n    //                     'Content-Type': 'application/json'\n    //                 },\n    //                 method: 'POST',\n    //                 json: true,\n    //                 body: {payload:{text: text, bot: bot, message: message, faq: faq}},\n    //                 // }).then(response => {\n    //                 }, function(err, response, json){\n    //                     if (err) {\n    //                         bot_answer.text = err +' '+ response.text;\n    //                         bot_answer.type =  MessageConstants.MESSAGE_TYPE.TEXT;\n    //                         winston.error(\"Error from webhook reply of getParsedMessage\", err);\n    //                         return resolve(bot_answer);\n    //                     }\n    //                     // if (response.statusCode >= 400) {                  \n    //                     //     return reject(`HTTP Error: ${response.statusCode}`);\n    //                     // }\n    //                     winston.debug(\"webhookurl repl_message \", response);\n\n    //                     var text = undefined;\n    //                     if(json && json.text===undefined) {\n    //                         text = 'Field text is not defined in the webhook respose of the faq with id: '+ faq._id+ \". Error: \" + JSON.stringify(response);\n    //                     }else {\n    //                         text = json.text;\n    //                     }\n\n\n    //                     that.getParsedMessage(text,message, bot, faq).then(function(bot_answer) {\n    //                         return resolve(bot_answer);\n    //                     });\n    //                 });\n             \n    //         }else {\n    //             winston.debug(\"repl_message \", repl_message)\n    //             return resolve(repl_message);\n    //         }\n\n\n           \n    //     });\n    // }\n\n\n    getBotMessage(botAnswer, projectid, bot, message, threshold) {\n        var that = this;\n          return new Promise(function(resolve, reject) {\n  \n              winston.debug('botAnswer', botAnswer);\n                // var found = false;\n                var bot_answer={};\n  \n                      if (!botAnswer ) {                          \n  \n                        var query = { \"id_project\": projectid, \"id_faq_kb\": bot._id, \"question\": \"defaultFallback\"};\n                        winston.debug('query', query);\n\n                       \n                        Faq.find(query) \n                        .lean().             //fai cache  \n                         exec(function (err, faqs) {\n                           if (err) {\n                             return res.status(500).send({ success: false, msg: 'Error getting object.' });\n                           }\n            \n                           winston.debug(\"faqs\", faqs);  \n\n                           if (faqs && faqs.length>0) {\n                                winston.debug(\"faqs exact\", faqs);  \n\n                                bot_answer.text=faqs[0].answer;   \n                                \n                                winston.debug(\"bot_answer exact\", bot_answer);  \n                                // found = true;\n                                // return resolve(bot_answer);\n\n                                // problem with \n                                // if (message.channel.name == \"chat21\") {    //why this contition on chat21 channel? bacause only chat21 support parsed replies?\n                                    winston.debug(\"faqBotSupport message.channel.name is chat21\",message);\n                                    that.getParsedMessage(bot_answer.text,message, bot, faqs[0]).then(function(bot_answerres) {\n                                    \n                                        bot_answerres.defaultFallback=true;\n    \n                                        return resolve(bot_answerres);\n                                    });\n\n                                // } else {\n                                //     winston.debug(\"faqBotSupport message.channel.name is not chat21 returning default\",message);\n                                //     return resolve(bot_answer);\n                                // }\n                                \n                           } else {\n                                var message_key = \"DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE\";                             \n                                bot_answer.text = that.getMessage(message_key, message.language, faqBotSupport.LABELS);  \n                                bot_answer.defaultFallback = true;\n                                // console.log(\"bot_answer \", bot_answer)\n                                return resolve(bot_answer);\n                           }\n                        });                        \n                      } \n                              \n  \n      });\n  \n      }\n\n\n  \n}\n\n\nvar faqBotSupport = new FaqBotSupport();\n\nfaqBotSupport.LABELS = {\n    EN : {\n        DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: \"I did not find an answer in the knowledge base. \\n Please reformulate your question?\"\n    },\n    IT : {\n        DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: \"Non sono in grado di fornirti una risposta adeguata. \\n Prego riformula la domanda.\"\n    },\n    \"IT-IT\" : {\n        DEFAULT_NOTFOUND_NOBOT_SENTENCE_REPLY_MESSAGE: \"Non sono in grado di fornirti una risposta adeguata. \\n Prego riformula la domanda.\"\n    }\n}\n\nmodule.exports = faqBotSupport;"
  },
  {
    "path": "services/faqService.js",
    "content": "var Faq = require(\"../models/faq\");\nvar Faq_kb = require(\"../models/faq_kb\");\nvar winston = require('../config/winston');\nconst botEvent = require('../event/botEvent');\nconst chatbotTypes = require(\"../models/chatbotTypes\");\nconst chatbotTemplates = require(\"../models/chatbotTemplates\");\nconst templates = require('../template/chatbot');\n\nclass FaqService {\n\n  create(id_project, id_user, data) {\n\n    return new Promise((resolve, reject) => {\n\n      var newFaq_kb = new Faq_kb({\n        name: data.name,\n        slug: data.slug,\n        description: data.description,\n        url: data.url,\n        id_project: id_project,\n        webhook_url: data.webhook_url,\n        webhook_enabled: data.webhook_enabled,\n        type: data.type,\n        subtype: data.subtype,\n        language: data.language,\n        public: false,\n        certified: false,\n        mainCategory: data.mainCategory,\n        intentsEngine: data.intentsEngine,\n        trashed: false,\n        createdBy: id_user,\n        updatedBy: id_user,\n        attributes: data.attributes\n      });\n\n      newFaq_kb.save((err, savedFaq_kb) => {\n\n        if (err) {\n          winston.error('(FaqService) error saving new chatbot ', err)\n          return reject('Error saving object.');\n        }\n\n        winston.debug('(FaqService) saved chatbot ', savedFaq_kb.toObject())\n        botEvent.emit('faqbot.create', savedFaq_kb);\n\n        winston.debug('type ' + data.type)\n\n        if (data.type === \"internal\" || data.type === \"tilebot\") {\n\n          let template = this.#resolveTemplate(data.subtype, data.template);\n          winston.debug('template ' + template);\n\n          let options = {};\n          if (data.namespace_id) {\n            options.namespace_id = data.namespace_id;\n          }\n\n          this.createGreetingsAndOperationalsFaqs(savedFaq_kb._id, savedFaq_kb.createdBy, savedFaq_kb.id_project, template, options);\n\n        } else {\n          winston.debug('(FaqService) Chatbot type: external bot');\n        }\n\n        return resolve(savedFaq_kb);\n      });\n\n    });\n  }\n\n  #resolveTemplate(subtype, template) {\n    subtype = chatbotTemplates[subtype] ? subtype : 'chatbot';\n    const { templates, default: defaultTemplate } = chatbotTemplates[subtype];\n    if (template && templates.includes(template)) {\n      return template;\n    }\n    return defaultTemplate;\n  }\n\n  createGreetingsAndOperationalsFaqs(faq_kb_id, created_by, projectid, template, options) {\n    \n    return new Promise( async (resolve, reject) =>  {\n\n      winston.debug('(FaqService) create faqs from template: ' + template);\n\n      let faqsArray = templates[template](options);\n\n      faqsArray.forEach(faq => {\n\n        var newFaq = new Faq({\n          id_faq_kb: faq_kb_id,\n          intent_id: faq.intent_id,\n          question: faq.question,\n          answer: faq.answer,\n          actions: faq.actions,\n          reply: faq.reply,\n          intent_display_name: faq.intent_display_name,\n          language: \"en\",\n          attributes: faq.attributes,\n          id_project: projectid,\n          topic: faq.topic,\n          createdBy: created_by,\n          updatedBy: created_by\n        });\n\n        newFaq.save(function (err, savedFaq) {\n          if (err) {\n            winston.error('(FaqService) error saving faq: ', err)\n            return reject({ success: false, msg: 'Error saving object.', err: err });\n          }\n\n          winston.debug('(FaqService) saved new faq for chatbot ' + savedFaq.id_faq_kb);\n\n        })\n      });\n    });\n  }\n\n  getAll(faq_kb_id) {\n\n    winston.debug(\"(FaqService) Get all faq for chatbot: \", faq_kb_id);\n\n    return new Promise((resolve, reject) => {\n\n      Faq.find({ id_faq_kb: faq_kb_id }, (err, faqs) => {\n        if (err) {\n          reject(err);\n        }\n        resolve(faqs);\n      }).lean().exec()\n    })\n  }\n\n\n}\n\nvar faqService = new FaqService();\nmodule.exports = faqService;\n"
  },
  {
    "path": "services/fileGridFsService.js",
    "content": "\n\n\n\nconst mongoose = require(\"mongoose\");\nconst GridFsStorage = require(\"multer-gridfs-storage\");\nconst uuidv4 = require('uuid/v4');\nvar config = require('../config/database');\nvar winston = require('../config/winston');\nvar pathlib = require('path');\n\nconst FileService = require(\"./fileService\");\n\n\n\nclass FileGridFsService extends FileService {\n\n    constructor(bucketName) {\n        super();\n        // DB\n        this.mongoURI = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database;\n\n        if (process.env.NODE_ENV === 'test') {\n            this.mongoURI = config.databasetest;\n        }\n       \n\n        this.retentionPeriod = 2592000; //30 days\n                            //    2147483647 //max accepted\n        if (process.env.ATTACHMENT_RETENTION_PERIOD) {\n            this.retentionPeriod = parseInt(process.env.ATTACHMENT_RETENTION_PERIOD);\n        }\n\n \n        this.enable_retention = false;\n        if (process.env.ENABLE_ATTACHMENT_RETENTION === true || process.env.ENABLE_ATTACHMENT_RETENTION === 'true' ) {\n            this.enable_retention = true;\n            winston.info(\"Attachments retention enabled with period \" + this.retentionPeriod + \" seconds\");\n        } else {\n            winston.info(\"Attachments retention disabled\");\n        }\n\n        // // connection\n        this.conn = mongoose.createConnection(this.mongoURI, {\n            useNewUrlParser: true,\n            useUnifiedTopology: true\n        });\n\n\n        // init gfs\n        \n        this.conn.once(\"open\", () => {\n            // console.log(\"mongoURI connected\")\n        // init stream\n            this.gfs = new mongoose.mongo.GridFSBucket(this.conn.db, {\n                bucketName: bucketName\n            });\n\n            if (this.enable_retention===true) {\n                this.createRetentionIndex(\"files.files\");\n                this.createRetentionIndex(\"files.chunks\");\n                this.createRetentionIndex(\"images.files\");\n                this.createRetentionIndex(\"images.chunks\");\n               \n            }\n        });\n\n   \n\n    }\n\n    createRetentionIndex(name) {\n        this.conn.db.collection(name)\n\n            .createIndex( { \"metadata.expireAt\": 1 },\n                          { expireAfterSeconds: 0 }, \n            // .createIndex( { \"uploadDate\": 1 },\n            //               { expireAfterSeconds: this.retentionPeriod }, \n\n\n                          (err, result) => {\n            if (err) {\n                winston.error('Error creating index with name ' + name, err);\n                return;\n            }\n\n            winston.info('Index ' + name +'  created successfully:', result);\n        });\n    }\n\n    async createFile ( filename, data, path, contentType, options) {\n\n        var metadata = undefined;\n        if (options && options.metadata) {\n            metadata = options.metadata;\n        }\n        const streamOptions = {\n            metadata: metadata,\n        };\n        if (contentType) {\n            streamOptions.contentType = contentType;\n        }\n        const stream = await this.gfs.openUploadStream(filename, streamOptions);\n        \n        await stream.write(data);\n        stream.end();\n        // console.log(\"stream\",stream)\n        return new Promise((resolve, reject) => {\n            stream.on('finish', resolve);\n            stream.on('error', reject);\n          });\n    }\n\n    async find(filename) {\n        return new Promise(async (resolve, reject) => {\n            let files = await this.gfs.find({filename: filename}).toArray();\n            winston.debug(\"files\", files);\n                if (files.length>0) {                     \n                        return resolve(files[0]);                                       \n                } else {\n                    return reject({code:\"ENOENT\", msg:\"File not found\"});\n                }\n        });\n    }\n    async deleteFile(filename) {\n        return new Promise(async (resolve, reject) => {\n            let files = await this.gfs.find({filename: filename}).toArray();\n            winston.debug(\"files\", files);\n                if (files.length>0) {\n                    this.gfs.delete(files[0]._id, function(error) {   \n                        if (error) {\n                            winston.error(\"Error deleting gfs file\", error);\n                            return reject(error);\n                        }                     \n                        return resolve(files[0]);\n                    });\n                   \n                } else {\n                    return reject({msg:\"File not found\"});\n                }\n        });\n    }\n\n    getFileDataAsStream (filename) {\n        // try {\n        var stream = this.gfs.openDownloadStreamByName(filename);\n\n        // stream.on('error', function(e) {\n        //     console.error(\"TTTTT\",e);\n        // });\n        // } catch (e) {\n        //     console.error(\"Error0000 getFileDataAsStream\");\n        //     return reject(e);\n        // }\n        return stream;\n    }\n    getFileDataAsBuffer (filename) {\n        var that = this;\n        return new Promise((resolve, reject) => {\n\n            try {\n                var stream = that.getFileDataAsStream(filename);\n            } catch (e) {\n                console.error(\"Error getFileDataAsStream\");\n                return reject(e);\n            }\n            \n            // if (!stream) {\n            //     console.error(\"Error getFileDataAsStream\");\n            //     return reject(e);\n            // }\n            \n\n            const bufs = [];\n            stream.on('error', function(e){\n                console.error(\"Error getFileDataAsStream\");\n                return reject(e);\n            })\n            stream.on('data', (data) => {\n                bufs.push(Buffer.isBuffer(data) ? data : Buffer.from(data));\n            });\n            stream.on('end', () => {\n                var buffer = Buffer.concat(bufs);\n                return resolve(buffer);\n            });\n        });\n    }\n\n    getStorage(folderName) {\n        const storageMongo = new GridFsStorage({\n            url: this.mongoURI,\n            options: { useNewUrlParser: true, useUnifiedTopology: true },\n            file: (req, file) => {\n                var folder = uuidv4();\n\n                // var form = new multiparty.Form();\n\n                // form.parse(req, function(err, fields, files) {\n                //     console.log(\"XXX fields\",fields)\n                //     console.log(\"XXX files\",files)\n                // });\n\n                // console.log(\"XXX req\",req)\n                // console.log(\"XXX req.query\",JSON.stringify(req.query))\n                // console.log(\"XXX req.body\",JSON.stringify(req.body))\n                // console.log(\"XXX req.folder\",JSON.stringify(req.folder))\n                // console.log(\"XXX req.headers\",JSON.stringify(req.headers))\n                // console.log(\"XXX file\",file)\n\n                // if (req.body.folder) {\n\n                //     folder = req.body.folder;\n                // }\n\n                var subfolder = \"/public\";\n                if (req.user && req.user.id) {\n                    subfolder = \"/users/\" + req.user.id;\n                }\n                const path = 'uploads' + subfolder + \"/\" + folderName + \"/\" + folder;\n                req.folder = folder;\n                // const match = [\"image/png\", \"image/jpeg\"];\n\n                // if (match.indexOf(file.mimetype) === -1) {\n                //     const filename = `${Date.now()}-${file.originalname}`;\n                //     return filename;\n                // }\n\n                // console.log(\"Date.now()\",Date.now())\n                // console.log(\"this.retentionPeriod*1000\",this.retentionPeriod*1000)\n                // console.log(\"Date.now()+this.retentionPeriod*1000\",Date.now()+this.retentionPeriod*1000)\n\n                let expireAt = new Date(Date.now()+ this.retentionPeriod*1000) ;\n                if (req.expireAt) {\n                    expireAt = req.expireAt;\n                }\n\n                return {\n                    bucketName: folderName,\n                    filename: `${path}/${file.originalname}`,\n                    metadata: {'expireAt': expireAt}\n                };\n            }\n        });\n\n        return storageMongo;\n    }\n\n\n\n\n\n\n\n    getStorageFixFolder(folderName) {\n        const storageMongo = new GridFsStorage({ \n            url : this.mongoURI,\n            options: { useNewUrlParser: true, useUnifiedTopology: true },\n            file: async (req, file) => {\n                // var folder = uuidv4();\n\n                // var form = new multiparty.Form();\n\n                // form.parse(req, function(err, fields, files) {\n                //     console.log(\"XXX fields\",fields)\n                //     console.log(\"XXX files\",files)\n                // });\n\n                // console.log(\"XXX req\",req)\n                // console.log(\"XXX req.query\",JSON.stringify(req.query))\n                // console.log(\"XXX req.body\",JSON.stringify(req.body))\n                // console.log(\"XXX req.folder\",JSON.stringify(req.folder))\n                // console.log(\"XXX req.headers\",JSON.stringify(req.headers))\n                // console.log(\"XXX file\",file)\n\n                // if (req.query.folder) {                    \n                //     folder = req.query.folder;\n                // }\n\n               \n\n                var subfolder = \"/public\";\n                if (req.user && req.user.id) {\n                  subfolder = \"/users/\"+req.user.id;\n                }\n                const path = 'uploads'+ subfolder + \"/\" + folderName ;\n                // req.folder = folder;\n\n                // const match = [\"image/png\", \"image/jpeg\"];\n    \n                // if (match.indexOf(file.mimetype) === -1) {\n                //     const filename = `${Date.now()}-${file.originalname}`;\n                //     return filename;\n                // }\n    \n                var pathExists = `${path}/${file.originalname}`;\n                winston.debug(\"pathExists\", pathExists);\n\n                let fileExists = await this.gfs.find({filename: pathExists}).toArray();\n                winston.debug(\"fileExists\", fileExists);\n                \n                if (fileExists && fileExists.length>0) {\n                    req.upload_file_already_exists = true;\n                    winston.debug(\"file already exists\", pathExists);\n                    return;\n                }\n                \n\n                 let expireAt = new Date(Date.now()+ this.retentionPeriod*1000) ;\n                if (req.expireAt) {\n                    expireAt = req.expireAt;\n                }\n\n                return {\n                    bucketName: folderName,\n                    filename: `${path}/${file.originalname}`,\n                    metadata: {'expireAt': expireAt}\n                };\n            }\n            });\n\n       return storageMongo;     \n    }\n\n\n\n\n\n\n\n\n\n\n    getStorageAvatar(folderName) {\n        const storageMongo = new GridFsStorage({ \n            url : this.mongoURI,\n            options: { useNewUrlParser: true, useUnifiedTopology: true },\n            file: async (req, file) => {\n                var filename = \"photo.jpg\";\n                // var folder = uuidv4();\n\n                // var form = new multiparty.Form();\n\n                // form.parse(req, function(err, fields, files) {\n                //     console.log(\"XXX fields\",fields)\n                //     console.log(\"XXX files\",files)\n                // });\n\n                // console.log(\"XXX req\",req)\n                // console.log(\"XXX req.query\",JSON.stringify(req.query))\n                // console.log(\"XXX req.body\",JSON.stringify(req.body))\n                // console.log(\"XXX req.folder\",JSON.stringify(req.folder))\n                // console.log(\"XXX req.headers\",JSON.stringify(req.headers))\n                // console.log(\"XXX file\",file)\n\n                // if (req.body.folder) {\n                    \n                //     folder = req.body.folder;\n                // }\n\n               \n\n                var subfolder = \"/public\";\n                if (req.user && req.user.id) {\n                  var userid = req.user.id;\n\n                  if (req.query.bot_id) {\n                    winston.debug(\"req.query.user_id: \"+ req.query.user_id);\n                    // winston.info(\"req.projectuser \",req.projectuser);\n\n                    // TODO pass also project_id ?user_id=abc&project_id=123 and find PU\n                    // if (req.project_user && req.project_user.role === ) {\n\n                    // }\n                    userid = req.query.bot_id;\n                  }\n                  subfolder = \"/users/\"+userid;\n                }\n                const path = 'uploads'+ subfolder + \"/\" + folderName ;\n                // req.folder = folder;\n\n                // const match = [\"image/png\", \"image/jpeg\"];\n    \n                // if (match.indexOf(file.mimetype) === -1) {\n                //     const filename = `${Date.now()}-${file.originalname}`;\n                //     return filename;\n                // }\n    \n                var pathExists = `${path}/${filename}`;\n                winston.debug(\"pathExists: \"+ pathExists);\n\n                let fileExists = await this.gfs.find({filename: pathExists}).toArray();\n                winston.debug(\"fileExists\", fileExists);\n                \n                if (fileExists && fileExists.length>0) {\n\n                    if (req.query.force) {\n                        try {\n                            await this.deleteFile(pathExists);\n                        \n                            let thumbFilename = 'thumbnails_200_200-'+filename;\n                            winston.info(\"thumbFilename:\"+thumbFilename);\n                        \n                            let thumbPath = pathExists.replace(filename,thumbFilename);\n                            winston.info(\"thumbPath:\"+thumbPath);\n                        \n                            await this.deleteFile(thumbPath);\n                        } catch(e) {\n                            winston.error(\"Error deleting forced old image:\",e);\n                        }\n                        \n    \n                    } else {\n\n                        req.upload_file_already_exists = true;\n                        winston.debug(\"file already exists\", pathExists);\n                        return;\n                    }\n                    \n\n                }\n                \n\n                // let expireAt = new Date(Date.now()+ this.retentionPeriod*1000) ;\n                // if (req.expireAt) {\n                //     expireAt = req.expireAt;\n                // }\n\n\n                return {\n                    bucketName: folderName,\n                    filename: `${path}/${filename}`,\n                    // metadata: {'expireAt': expireAt}\n                };\n            }\n            });\n\n       return storageMongo;     \n    }\n\n    /**\n     * Storage for avatar/profile photos in files bucket but with fixed path structure\n     * Path: uploads/users/{user_id|bot_id}/images/photo.jpg\n     * This maintains compatibility with clients that expect fixed paths\n     */\n    getStorageAvatarFiles(folderName) {\n        const storageMongo = new GridFsStorage({ \n            url : this.mongoURI,\n            options: { useNewUrlParser: true, useUnifiedTopology: true },\n            file: async (req, file) => {\n                var filename = \"photo.jpg\";\n               \n                var subfolder = \"/public\";\n                if (req.user && req.user.id) {\n                  var userid = req.user.id;\n\n                  if (req.query.bot_id) {\n                    winston.debug(\"req.query.bot_id: \"+ req.query.bot_id);\n                    userid = req.query.bot_id;\n                  }\n                  subfolder = \"/users/\"+userid;\n                }\n                // Use \"images\" in path for backward compatibility with clients\n                const path = 'uploads'+ subfolder + \"/images\" ;\n    \n                var pathExists = `${path}/${filename}`;\n                winston.debug(\"pathExists: \"+ pathExists);\n\n                let fileExists = await this.gfs.find({filename: pathExists}).toArray();\n                winston.debug(\"fileExists\", fileExists);\n                \n                // Always delete old photo and thumbnail if they exist (allow profile photo updates)\n                if (fileExists && fileExists.length>0) {\n                    try {\n                        await this.deleteFile(pathExists);\n                        winston.debug(\"Deleted old profile photo:\", pathExists);\n                    \n                        let thumbFilename = 'thumbnails_200_200-'+filename;\n                        let thumbPath = pathExists.replace(filename,thumbFilename);\n                        winston.debug(\"thumbPath:\"+thumbPath);\n                    \n                        try {\n                            await this.deleteFile(thumbPath);\n                            winston.debug(\"Deleted old thumbnail:\", thumbPath);\n                        } catch(thumbErr) {\n                            // Thumbnail might not exist, that's ok\n                            winston.debug(\"Thumbnail not found or already deleted:\", thumbPath);\n                        }\n                    } catch(e) {\n                        winston.error(\"Error deleting old profile photo:\",e);\n                        // Continue anyway - the new upload will overwrite\n                    }\n                }\n\n                // Avatar/profile photos have no retention by default\n                // Only set expireAt if explicitly provided in req.expireAt\n                let returnObj = {\n                    bucketName: folderName,\n                    filename: `${path}/${filename}`\n                };\n                \n                if (req.expireAt) {\n                    returnObj.metadata = {'expireAt': req.expireAt};\n                }\n\n                return returnObj;\n            }\n        });\n\n       return storageMongo;     \n    }\n\n    /**\n     * Storage for project assets (logos, documents, etc.)\n     * Path: uploads/projects/{project_id}/assets/{uuid}/filename\n     * Assets belong to the project, not to the user who uploads them\n     */\n    getStorageProjectAssets(folderName) {\n        const storageMongo = new GridFsStorage({\n            url: this.mongoURI,\n            options: { useNewUrlParser: true, useUnifiedTopology: true },\n            file: (req, file) => {\n                var folder = uuidv4();\n\n                // Try to get project ID from req.project._id or req.projectid\n                let projectId;\n                if (req.project && req.project._id) {\n                    projectId = req.project._id.toString();\n                } else if (req.projectid) {\n                    projectId = req.projectid.toString();\n                } else {\n                    winston.error(\"Project not found in request for asset upload\");\n                    throw new Error(\"Project is required for asset upload\");\n                }\n\n                const path = 'uploads/projects/' + projectId + \"/\" + folderName + \"/\" + folder;\n                req.folder = folder;\n\n                // Assets have no retention by default\n                // Only set expireAt if explicitly provided in req.expireAt\n                let returnObj = {\n                    bucketName: folderName,\n                    filename: `${path}/${file.originalname}`\n                };\n                \n                if (req.expireAt) {\n                    returnObj.metadata = {'expireAt': req.expireAt};\n                }\n\n                return returnObj;\n            }\n        });\n\n        return storageMongo;\n    }\n\n}\n\n\nmodule.exports = FileGridFsService;"
  },
  {
    "path": "services/fileService.js",
    "content": "class FileService {\n\n    constructor() {}\n\n    createFile ( fileName, data, contentType, options) {}\n    getFileData (filename) {}\n    deleteFile(fileName) {}\n\n}\n\n// var fileService = new FileService();\n\nmodule.exports = FileService;"
  },
  {
    "path": "services/filesystemService.js",
    "content": "\nclass FilesystemService extends FileService {\n\n    createFile ( fileName, data, contentType, options);\n    getFileData (filename);\n\n}\n\nvar filesystemService = new FilesystemService();\n\nmodule.exports = filesystemService;"
  },
  {
    "path": "services/geoService.js",
    "content": "'use strict';\n\nvar requestEvent = require(\"./../event/requestEvent\");\nvar Location = require(\"./../models/location\");\nvar Request = require(\"./../models/request\");\n\n\nvar winston = require('../config/winston');\nvar geoip = require('geoip-lite');\n\nclass GeoService {\n\n    constructor() {\n        this.enabled = true;\n        if (process.env.GEO_SERVICE_ENABLED==\"false\" || process.env.GEO_SERVICE_ENABLED==false) {\n            this.enabled = false;\n        }\n        winston.debug(\"GeoService this.enabled: \"+ this.enabled);\n    }\n\n\n  // https://medium.com/@rossbulat/node-js-client-ip-location-with-geoip-lite-fallback-c25833c94a76\n  // https://www.npmjs.com/package/geoip-lite\n  // https://www.npmjs.com/package/geoip-country\n\n  listen() {\n    \n    if (this.enabled==true) {\n        winston.info(\"GeoService listener started\");\n    } else {\n        return winston.info(\"GeoService listener disabled\");\n    }\n    \n\n\n \n  // var ip = \"95.255.73.34\";\n  // var geo = geoip.lookup(ip);\n  \n  // console.log(\"geo\", geo);\n\n  // { range: [ 1610565632, 1610566143 ],\n  //     country: 'IT',\n  //     region: '65',\n  //     eu: '1',\n  //     timezone: 'Europe/Rome',\n  //     city: 'Roseto degli Abruzzi',\n  //     ll: [ 42.6716, 14.0148 ],\n  //     metro: 0,\n  //     area: 200 }\n\n\n\n  var requestCreateKey = 'request.create';\n  if (requestEvent.queueEnabled) {\n    requestCreateKey = 'request.create.queue';\n  }\n  winston.debug('GeoService requestCreateKey: ' + requestCreateKey);\n\n\n  requestEvent.on(requestCreateKey, function(request) {\n\n    setImmediate(() => { \n\n    winston.debug(\"request\", request);\n\n    var ip = (request.location && request.location.ipAddress) || (request.attributes && request.attributes.ipAddress);\n    winston.debug(\"ip\" + ip);\n    if (ip) {\n        var geo = geoip.lookup(ip);  \n        winston.debug(\"Geo result\", geo);\n\n        \n        if (geo) {\n            var update = {};\n\n            if (!request.location) {\n                request.location = {};\n            }\n\n            if (geo.country && !request.location.country) {\n                winston.debug(\"geo.country:\"+ geo.country);\n                // request.location.country = geo.country;\n                update[\"location.country\"] = geo.country;\n\n            }\n            if (geo.region && !request.location.region) {\n                winston.debug(\"geo.region: \"+ geo.region);\n                // request.location.region = geo.region;\n                update[\"location.region\"] = geo.region;\n            }\n            if (geo.city && !request.location.city) {\n                winston.debug(\"geo.city: \" + geo.city);\n                // request.location.city = geo.city;\n                update[\"location.city\"] = geo.city;\n            }\n\n            if (!request.location.ipAddress) {\n                winston.debug(\"request.location.ipAddress: \" + request.location.ipAddress);\n                // request.location.ipAddress = ip;\n                update[\"location.ipAddress\"] = ip;\n            }\n\n            // console.log(request.location.toString());\n            \n\n            // var locFound=false;\n            // try {\n            //     var loc = request.location.geometry;\n            //     locFound = true;\n            // } catch (e) {locFound = false;}\n            \n            winston.debug(\"geo.ll: \" + geo.ll);\n            winston.debug(\"request.location: \" + request.location);\n            winston.debug(\"request.location.geometry: \" + request.location.geometry);\n            \n            \n            if (geo.ll && (!request.location.geometry || \n                    (request.location.geometry && request.location.geometry.coordinates && request.location.geometry.coordinates.length==0) \n                ) ) {\n                // if (geo.ll && request.location.geometry != undefined) {\n                winston.debug(\"geo.ll: \" + geo.ll);\n                // request.location.geometry = {type: \"Point\", coordinates: geo.ll};\n                update[\"location.geometry\"]  = {type: \"Point\", coordinates: geo.ll};\n            }\n            \n            \n            // var setObj = { $set: {location: update} }        \n            // winston.info(\"setObj\", setObj);\n            // winston.info(\"update\", update);\n\n            winston.debug(\"geo request saving\", update);\n            // winston.debug(\"geo request saving\", request);\n\n\n            // if (request.markModified) {\n            //     request.markModified('location');\n            // }\n            \n\n            //when queue is enabled request.save is undefined because request is a plain object\n            // request.save(function(err, reqL) {            \n            return Request.findByIdAndUpdate(request.id, update, { new: true, upsert: false }).exec( function(err, reqL) {                \n                if (err) {\n                    return winston.error(\"Error saving location metadata for request with id \" + request._id, err);\n                }\n                return winston.verbose(\"Saved location metadata for request with id \" + request._id);\n            }); \n\n            //TODO AGGIORNA ANCHE LEAD e req.snapshot.lead?\n            // leggi ip da request e nn da attributes\n               \n        }\n    }\n    });\n    });\n}\n\n\n}\nvar geoService = new GeoService();\n\n\nmodule.exports = geoService;\n"
  },
  {
    "path": "services/integrationService.js",
    "content": "const winston = require('../config/winston');\nconst integrations = require('../models/integrations');\n\nclass IntegrationService {\n\n  \n\n    async getIntegration(id_project, integration_name) {\n        if (!id_project || !integration_name) {\n            throw({ code: 422, error: \"Fields 'id_project', 'integration_name' are mandatory\" });\n        }\n\n        try {\n            const integration = await integrations.findOne({ id_project: id_project, name: integration_name});\n            if (!integration && integration_name !== 'openai') {\n                throw({ code: 404, error: `Integration ${integration_name} not found for project ${id_project}` });\n            }\n\n            return integration;\n\n        } catch (err) {\n            winston.error(\"Erro getting integration: \", err);\n            throw({ code: err.code || 500, error: err.error || `Error getting integration for ${integration_name} for project ${id_project}` })\n        }\n    }\n\n    async getKeyFromIntegration(id_project, integration_name = 'openai') {\n        if (!id_project || !integration_name) {\n            throw({ code: 422, error: \"Fields 'id_project', 'integration_name' are mandatory\" });\n        }\n\n        try {\n            const integration = await integrations.findOne({ id_project: id_project, name: integration_name});\n            if (!integration && integration_name !== 'openai') {\n                throw({ code: 404, error: `Integration ${integration_name} not found for project ${id_project}` });\n            }\n\n            return integration?.value?.apikey;\n\n        } catch (err) {\n            winston.error(\"Error getting integration: \", err);\n            throw({ code: err.code || 500, error: err.error || `Error getting integration for ${integration_name} for project ${id_project}` })\n        }\n    }\n}\n\nconst integrationService = new IntegrationService();\n\nmodule.exports = integrationService;\n\n"
  },
  {
    "path": "services/labelService-no-default.js",
    "content": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\nvar fs = require('fs');\nvar path = require('path');\nvar labelsDir = __dirname+\"/../config/labels/\";\nwinston.debug('labelsDir: ' + labelsDir);\nvar cacheUtil = require('../utils/cacheUtil');\n\nclass LabelService {\n\n// fetch pivot default language (EN)\nfetchPivotDefault() {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n        that.fetchDefault().then(function (def) {\n            // console.log(\"def\", def)\n            var pivot = def.find(l => l.lang === \"EN\");\n            return resolve(pivot);        \n        });\n    });\n   \n}\n\n// fetch all widget.json languages\nfetchDefault() {\n     \n    var that = this;\n    return new Promise(function (resolve, reject) {\n\n        var filePath = path.join(labelsDir, 'widget.json');\n    \n        fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){\n        if (err) {\n            winston.error('Error getting labels', err);\n            return reject({ msg: 'Error reading object.' });\n        }\n            winston.debug('label fetched', data);\n           return resolve(JSON.parse(data));\n        });\n    });\n}\n\n// get a specific key of a project language merged with default (widget.json) but if not found return Pivot\nasync get(id_project, language, key) {\n   var ret = await this.getByLanguageAndKey(id_project, language, key);\n\n   if (ret) {\n       return ret;\n   } else { \n       return this.getByLanguageAndKey(id_project, \"EN\", key);\n   }\n\n}\n\n// get a specific key of a project language merged with default (widget.json) with NO Pivot\ngetByLanguageAndKey(id_project, language, key) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n\n        that.getAllByLanguageNoPivot(id_project,language).then(function(returnval) {\n            winston.debug(\"getByLanguageAndKey returnval\",returnval);\n            if (returnval) {\n                var value = returnval.data[key];\n                winston.debug(\"getByLanguageAndKey value: \"+value);\n                 return resolve(value); \n            } else {\n                winston.debug(\"getByLanguageAndKey  return undefined\");\n                return resolve();\n            }\n        });\n    });\n}\n\n // get a specific project language merged with default (widget.json) but if not found return Pivot\nasync getAllByLanguage(id_project, language) {\n    var ret = await this.getAllByLanguageNoPivot(id_project, language);\n    winston.debug(\"getAllByLanguage ret\",ret);\n    if (ret) {\n        return ret;\n    } else { \n        var retEn = await this.getAllByLanguageNoPivot(id_project, \"EN\");\n        if (retEn) {\n            return retEn;\n        } else { \n            var retPiv = await this.fetchPivotDefault();\n            winston.debug(\"retPiv\",retPiv);\n            return retPiv;\n        }\n    }\n \n }\n\n // get a specific project language merged with default (widget.json) Not Pivot\ngetAllByLanguageNoPivot(id_project, language) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n\n        that.getAll(id_project).then(function(returnval) {\n            winston.debug(\"getAllByLanguageNoPivot returnval: \",returnval);\n\n            var pickedLang = returnval.data.find(l => l.lang === language);\n            //var pickedLang = returnval.data[req.params.lang];           \n\n            winston.debug(\"getAllByLanguageNoPivot pickedLang\"+  language,pickedLang);\n            return resolve(pickedLang); \n        });\n    });\n}\n\n// get all project languages merged with default (widget.json)\n//UNused\ngetAllMerged(id_project) {\n \n    var that = this;\n    return new Promise(function (resolve, reject) {\n        \n        return that.fetchDefault().then(function(defaults) {\n\n            var query = {\"id_project\": id_project};\n        \n            winston.debug(\"query /\", query);\n        \n        \n            return Label.findOne(query).lean()\n            //@DISABLED_CACHE .cache(cacheUtil.longTTL, id_project+\":labels:query:all\")\n            .exec(function (err, labels) {\n                if (err) {\n                    winston.error('Label ROUTE - REQUEST FIND ERR ', err)\n                    return reject({ msg: 'Error getting object.' });\n                }\n\n                winston.debug(\"here /\", labels);\n                let returnval;\n                if (!labels) {\n                    winston.debug(\"here no labels\");        \n                    returnval = {data: defaults};\n                } else {\n                    returnval = labels;\n                    defaults.forEach(elementDef => {\n                        var pickedLang = labels.data.find(l => l.lang === elementDef.lang);\n                        if (!pickedLang) {\n                        returnval.data.push(elementDef);\n                        }\n                    });                \n                }\n                \n                winston.debug(\"getAll returnval\",returnval);\n            \n                return resolve(returnval);\n                \n            });\n        });\n    });\n}\n\n\ngetAll(id_project) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n        \n        return that.fetchPivotDefault().then(function(def) {\n\n\n                var query = {\"id_project\": id_project};\n                    \n                winston.debug(\"query /\", query);\n\n\n                return Label.findOne(query).lean()\n                //@DISABLED_CACHE .cache(cacheUtil.longTTL, id_project+\":labels:query:all\")\n                .exec(function (err, labels) {\n                    if (err) {\n                        winston.error('Label ROUTE - REQUEST FIND ERR ', err)\n                        return reject({ msg: 'Error getting object.' });\n                    }\n\n                    winston.debug(\"here /\", labels);\n                    let returnval;\n                    if (!labels) {\n                        winston.debug(\"here no labels\");        \n                        returnval = {data: [def]};\n                    } else {\n                        returnval = labels;                       \n                    }\n                    \n                    winston.debug(\"getAll returnval\",returnval);\n                \n                    return resolve(returnval);\n                    \n                });\n        });\n    });\n}\n\n  \n\n}\nvar labelService = new LabelService();\nmodule.exports = labelService;"
  },
  {
    "path": "services/labelService.js",
    "content": "var Label = require(\"../models/label\");\nvar winston = require('../config/winston');\nvar fs = require('fs');\nvar path = require('path');\nvar labelsDir = __dirname+\"/../config/labels/\";\nwinston.debug('labelsDir: ' + labelsDir);\nvar cacheUtil = require('../utils/cacheUtil');\n\nclass LabelService {\n    \n    constructor() {\n        this.FALLBACK_LANGUAGE = \"EN\"; \n\n    }\n    \n\n// get a specific key of a project language but if not found return Pivot \nasync get(id_project, language, key) {\n   var ret = await this.getLanguage(id_project, language);\n   var value = ret.data[key];\n   return value;\n}\n\n // get a specific project language, if not found return FALLBACK_LANGUAGE, if not found return default FALLBACK_LANGUAGE\n\n async getLanguage(id_project, language) {\n    var that = this;\n    var returnval = await that.getAll(id_project);\n    winston.debug(\"getLanguage returnval: \",returnval);\n\n    if (!returnval) {\n        var retPiv = await that.fetchPivotDefault();\n        winston.debug(\"retPiv\",retPiv);\n        return retPiv;\n    }\n\n    var pickedLang = returnval.data.find(l => l.lang === language);\n\n    winston.debug(\"getLanguage pickedLang\"+  language,pickedLang);\n\n    if (pickedLang) {\n        return pickedLang; \n    } else {\n\n        var defaultLang = returnval.data.find(l => l.default === true);\n\n        if (defaultLang) {\n            return defaultLang; \n        }\n\n        var pivotLang = returnval.data.find(l => l.lang === that.FALLBACK_LANGUAGE);   // <-- NOT necessary but nice\n        if (pivotLang) {\n            return pivotLang; \n        } else {\n            var retPiv = await that.fetchPivotDefault();\n            winston.debug(\"retPiv\",retPiv);\n            return retPiv;\n        }\n    }\n        \n }\n\n\ngetAll(id_project) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n        \n        // return that.fetchPivotDefault().then(function(def) {\n\n\n                var query = {\"id_project\": id_project};\n                    \n                winston.debug(\"query /\", query);\n\n\n                return Label.findOne(query).lean()\n                //@DISABLED_CACHE .cache(cacheUtil.longTTL, id_project+\":labels:query:all\")  //label_cache\n                .exec(function (err, labels) {\n                    if (err) {\n                        winston.error('Label ROUTE - REQUEST FIND ERR ', err)\n                        return reject({ msg: 'Error getting object.' });\n                    }\n\n                    winston.debug(\"here /\", labels);\n                    let returnval;\n                    // if (labels) {\n                        returnval = labels;\n                    // }                    \n                    winston.debug(\"getAll returnval\",returnval);\n                \n                    return resolve(returnval);\n                    \n                });\n\n        });\n    // });\n}\n\n\n// fetch pivot default language (FALLBACK_LANGUAGE)\nfetchPivotDefault() {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n        that.fetchDefault().then(function (def) {\n            // console.log(\"def\", def)\n            var pivot = def.find(l => l.lang === that.FALLBACK_LANGUAGE);\n            return resolve(pivot);        \n        });\n    });\n   \n}\n\n// fetch all widget.json languages\nfetchDefault() {\n     \n    var that = this;\n    return new Promise(function (resolve, reject) {\n\n        var filePath = path.join(labelsDir, 'widget.json');\n    \n        fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){\n        if (err) {\n            winston.error('Error getting labels', err);\n            return reject({ msg: 'Error reading object.' });\n        }\n            winston.debug('label fetched', data);\n           return resolve(JSON.parse(data));\n        });\n    });\n}\n\n\n  \n\n}\nvar labelService = new LabelService();\nmodule.exports = labelService;"
  },
  {
    "path": "services/leadService.js",
    "content": "'use strict';\n\nvar Lead = require(\"../models/lead\");\nconst uuidv4 = require('uuid/v4');\nconst leadEvent = require('../event/leadEvent');\nvar winston = require('../config/winston');\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar phoneUtil = require('../utils/phoneUtil');\n\n\nclass LeadService {\n\n\n  findByEmail(email, id_project) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      return Lead.findOne({email: email, id_project: id_project})\n      //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, id_project+\":leads:email:\"+email)  //lead_cache\n      .exec(function(err, lead)  {\n          if (err) {\n            return reject(err);\n          }\n          if (!lead) {\n            return resolve(null);\n          }\n          return resolve(lead);\n      \n      });\n    });\n  }\n\n \n\n\n  createIfNotExists(fullname, email, id_project, createdBy, attributes, status) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      that.findByEmail(email, id_project).then(function(lead) {\n      // return Lead.findOne({email: email, id_project: id_project}, function(err, lead)  {         \n          if (!lead) {\n            return resolve(that.create(fullname, email, id_project, createdBy, attributes, status));\n          }\n          return resolve(lead);\n      \n      }).catch(function(err) {\n        return resolve(that.create(fullname, email, id_project, createdBy, attributes, status));\n      })\n    });\n  }\n\n  \n  create(fullname, email, id_project, createdBy, attributes, status) {\n    return this.createWitId(null, fullname, email, id_project, createdBy, attributes, status);\n  }\n\n\n\n  createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      return Lead.findOne({lead_id: lead_id, id_project: id_project})\n        //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, id_project+\":leads:lead_id:\"+lead_id) //lead_cache\n        .exec(function(err, lead)  {\n          if (err) {\n            winston.error(\"Error createIfNotExistsWithLeadId\", err);\n            return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone));\n          }\n          if (!lead) {\n            return resolve(that.createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone));\n          }\n\n          winston.debug(\"lead.email: \" + lead.email); \n          winston.debug(\"email: \" + email); \n          \n          if (lead.email == email) {\n            winston.debug(\"lead already exists createIfNotExistsWithLeadId with the same email\");\n            return resolve(lead);\n          } else {\n            winston.debug(\"lead already exists createIfNotExistsWithLeadId but with different email\");\n            return resolve(that.updateWitId(lead_id, fullname, email, id_project, status, phone));\n          }\n          \n      \n      });\n    });\n  }\n\n\n  updateStatusWitId(lead_id, id_project, status) {\n    winston.debug(\"lead_id: \"+ lead_id);\n    winston.debug(\"id_project: \"+ id_project);\n    winston.debug(\"status: \"+ status);\n\n    return new Promise(function (resolve, reject) {\n\n    var update = {};\n\n    update.status = status;\n\n    \n      Lead.findOneAndUpdate({lead_id:lead_id}, update, { new: true, upsert: true }, function (err, updatedLead) {\n        if (err) {\n          winston.error('Error updating lead ', err);\n          return reject(err);\n        }\n\n      \n        leadEvent.emit('lead.update', updatedLead);\n        return resolve(updatedLead);\n      });\n    });\n  }\n\n  updateWitId(lead_id, fullname, email, id_project, status, phone) {\n    winston.debug(\"updateWitId lead_id: \"+ lead_id);\n    winston.debug(\"fullname: \"+ fullname);\n    winston.debug(\"email: \"+ email);\n    winston.debug(\"id_project: \"+ id_project);\n    winston.debug(\"status: \"+ status);\n\n    return new Promise(function (resolve, reject) {\n\n    var update = {};\n\n    update.fullname = fullname;\n    update.email = email;\n    if (phone !== undefined) {\n      update.phone = phoneUtil.normalizePhone(phone);\n    }\n    if (status) {\n      update.status = status;\n    }\n    \n\n    \n      Lead.findOneAndUpdate({lead_id:lead_id}, update, { new: true, upsert: true }, function (err, updatedLead) {\n        if (err) {\n          winston.error('Error updating lead ', err);\n          return reject(err);\n        }\n\n      \n        leadEvent.emit('lead.update', updatedLead);\n        leadEvent.emit('lead.email.update', updatedLead);\n        leadEvent.emit('lead.fullname.update', updatedLead);\n        leadEvent.emit('lead.fullname.email.update', updatedLead);\n        return resolve(updatedLead);\n      });\n    });\n  }\n\n  createWitId(lead_id, fullname, email, id_project, createdBy, attributes, status, phone) {\n\n    if (!createdBy) {\n      createdBy = \"system\";\n    }\n\n    if (!lead_id) {\n      lead_id = uuidv4();\n    }\n\n    return new Promise(function (resolve, reject) {\n\n            var newLead = new Lead({\n              lead_id: lead_id,\n              fullname: fullname,\n              email: email,\n              phone: phoneUtil.normalizePhone(phone),\n              attributes: attributes,\n              status: status,\n              id_project: id_project,\n              createdBy: createdBy,\n              updatedBy: createdBy\n            });\n          \n            newLead.save(function(err, savedLead) {\n              if (err) {\n                winston.error('Error saving the lead '+ JSON.stringify(newLead), err)\n                return reject(err);\n              }            \n              winston.verbose('Lead created ', newLead.toJSON());\n\n              leadEvent.emit('lead.create', newLead);\n              return resolve(savedLead);\n            });\n        });\n\n\n  }\n\n}\nvar leadService = new LeadService();\n\n\nmodule.exports = leadService;\n"
  },
  {
    "path": "services/logsService.js",
    "content": "'use strict';\n\nconst { FlowLogs } = require(\"../models/flowLogs\");\nconst default_log_level = 'info';\n\nconst levels = { error: 0, warn: 1, info: 2, debug: 3, native: 4 };\n\nclass LogsService {\n\n    async getLastRows(id, limit, logLevel, queryField = 'request_id') {\n        let level = logLevel || default_log_level;\n        if (level === 'default') {\n            level = default_log_level\n        }\n        let nlevel = levels[level];\n\n        // Build match condition based on queryField\n        const matchCondition = queryField === 'webhook_id' \n            ? { [queryField]: id, longExp: { $exists: true } }\n            : { [queryField]: id };\n\n        return FlowLogs.aggregate([\n            { $match: matchCondition },\n            { $unwind: \"$rows\" },\n            { $match: { \"rows.nlevel\": { $lte: nlevel } } },\n            { $sort: { \"rows.timestamp\": -1, \"rows._id\": -1 } },\n            { $limit: limit }\n        ]).then(rows => rows.reverse())\n    }\n\n    async getOlderRows(id, limit, logLevel, timestamp, queryField = 'request_id') {\n        let level = logLevel || default_log_level;\n        if (level === 'default') {\n            level = default_log_level\n        }\n        let nlevel = levels[level];\n\n        // Build match condition based on queryField\n        const matchCondition = queryField === 'webhook_id' \n            ? { [queryField]: id, longExp: { $exists: true } }\n            : { [queryField]: id };\n\n        return FlowLogs.aggregate([\n            { $match: matchCondition },\n            { $unwind: \"$rows\" },\n            { $match: { \"rows.nlevel\": { $lte: nlevel }, \"rows.timestamp\": { $lt: timestamp } } },\n            { $sort: { \"rows.timestamp\": -1, \"rows._id\": -1 } },\n            { $limit: limit }\n        ]).then(rows => rows.reverse())\n    }\n\n    async getNewerRows(id, limit, logLevel, timestamp, queryField = 'request_id') {\n        let level = logLevel || default_log_level;\n        if (level === 'default') {\n            level = default_log_level\n        }\n        let nlevel = levels[level];\n\n        // Build match condition based on queryField\n        const matchCondition = queryField === 'webhook_id' \n            ? { [queryField]: id, longExp: { $exists: true } }\n            : { [queryField]: id };\n\n        return FlowLogs.aggregate([\n            { $match: matchCondition },\n            { $unwind: \"$rows\" },\n            { $match: { \"rows.nlevel\": { $lte: nlevel }, \"rows.timestamp\": { $gt: timestamp } } },\n            { $sort: { \"rows.timestamp\": 1, \"rows._id\": 1 } },\n            { $limit: limit }\n        ])\n    }\n    \n}\n\nlet logsService = new LogsService();\n\nmodule.exports = logsService;"
  },
  {
    "path": "services/mcpService.js",
    "content": "'use strict';\n\nconst axios = require('axios').default;\nconst winston = require('../config/winston');\n\n\n/**\n * MCP Service - Manages connections and calls to MCP servers\n * MCP (Model Context Protocol) uses JSON-RPC 2.0 for communication\n */\nclass MCPService {\n\n  constructor() {\n    // Cache of connections to MCP servers\n    // Key: `${projectId}_${url}` or just `${url}` if projectId is not provided\n    // Value: { config, sessionId?, initialized, capabilities }\n    this.connections = new Map();\n  }\n\n  /**\n   * Parses a response in SSE (Server-Sent Events) format and extracts the JSON object\n   * @param {String} sseData - String with SSE format\n   * @returns {Object} - Parsed JSON object\n   */\n  parseSSEResponse(sseData) {\n    const lines = sseData.split('\\n');\n    for (const line of lines) {\n      if (line.startsWith('data: ')) {\n        const jsonStr = line.substring(6); // Removes \"data: \"\n        try {\n          return JSON.parse(jsonStr);\n        } catch (e) {\n          throw new Error(`Failed to parse SSE JSON data: ${e.message}`);\n        }\n      }\n    }\n    throw new Error('No data field found in SSE response');\n  }\n\n  /**\n   * Sends a JSON-RPC request to an MCP server\n   * @param {String} url - MCP server URL\n   * @param {Object} request - JSON-RPC request\n   * @param {Object} auth - Authentication configuration (optional)\n   * @param {String} sessionId - Session ID for the MCP server (optional)\n   * @returns {Promise<Object>} - Server response (includes sessionId if present in headers)\n   */\n  async sendJSONRPCRequest(url, request, auth, sessionId) {\n    const config = {\n      method: 'POST',\n      url: url,\n      headers: {\n        'Content-Type': 'application/json',\n        'Accept': 'application/json, text/event-stream'\n      },\n      data: request,\n      timeout: 30000, // 30 seconds timeout\n      validateStatus: function (status) {\n        return status >= 200 && status < 500; // Accept also 4xx to capture headers\n      }\n    };\n\n    // Add session ID in mcp-session-id header if present\n    if (sessionId) {\n      config.headers['mcp-session-id'] = sessionId;\n    }\n\n    // Add authentication if present\n    if (auth) {\n      if (auth.type === 'bearer' && auth.token) {\n        config.headers['Authorization'] = `Bearer ${auth.token}`;\n      } else if (auth.type === 'api_key' && auth.key) {\n        config.headers['X-API-Key'] = auth.key;\n      } else if (auth.type === 'basic' && auth.username && auth.password) {\n        const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString('base64');\n        config.headers['Authorization'] = `Basic ${credentials}`;\n      }\n    }\n\n    try {\n      const response = await axios(config);\n\n      // Capture session ID from response headers (if present)\n      const sessionIdFromHeader = response.headers['mcp-session-id'] || \n                                  response.headers['x-mcp-session-id'];\n\n      // Parse response: could be SSE (text/event-stream) or direct JSON\n      let responseData;\n      if (typeof response.data === 'string') {\n        // SSE format: extracts JSON from \"data:\" line\n        responseData = this.parseSSEResponse(response.data);\n      } else {\n        // JSON response already parsed\n        responseData = response.data;\n      }\n\n      // Add session ID from response if present in headers\n      if (sessionIdFromHeader) {\n        responseData.sessionId = sessionIdFromHeader;\n      }\n\n      // Check if there's a JSON-RPC error\n      if (responseData.error) {\n        throw new Error(`MCP Error: ${responseData.error.message || 'Unknown error'} (code: ${responseData.error.code})`);\n      }\n\n      return responseData;\n    } catch (error) {\n      if (error.response) {\n        // HTTP error\n        const status = error.response.status;\n        const statusText = error.response.statusText;\n        const responseData = error.response.data;\n        const errorMessage = `HTTP Error ${status}: ${statusText}`;\n        const details = responseData ? ` - ${JSON.stringify(responseData)}` : '';\n        throw new Error(`${errorMessage}${details}`);\n      } else if (error.request) {\n        // No response received\n        throw new Error(`No response from MCP server: ${error.message}`);\n      } else {\n        // Request configuration error\n        throw new Error(`Request error: ${error.message}`);\n      }\n    }\n  }\n\n  /**\n   * Generates a unique cache key based on URL and projectId\n   * @param {String} url - MCP server URL\n   * @param {String} projectId - Project ID (optional)\n   * @returns {String} - Unique cache key\n   */\n  getCacheKey(url, projectId) {\n    return projectId ? `${projectId}_${url}` : url;\n  }\n\n  /**\n   * Initializes a connection to an MCP server\n   * @param {Object} serverConfig - MCP server configuration { url, projectId?, auth? }\n   * @returns {Promise<Object>} - Initialization result\n   */\n  async initializeServer(serverConfig) {\n    if (!serverConfig || !serverConfig.url) {\n      throw new Error('Server MCP configuration is missing or invalid');\n    }\n\n    const serverId = this.getCacheKey(serverConfig.url, serverConfig.projectId);\n\n    try {\n      // Initialize session (some MCP servers require session ID)\n      let sessionId = null;\n      let initializeResponse = null;\n      \n      try {\n        // JSON-RPC call to initialize the server\n        const response = await this.sendJSONRPCRequest(serverConfig.url, {\n          jsonrpc: '2.0',\n          id: Date.now(),\n          method: 'initialize',\n          params: {\n            protocolVersion: '2024-11-05',\n            capabilities: {\n              tools: {}\n            },\n            clientInfo: {\n              name: 'tiledesk-server',\n              version: '1.0.0'\n            }\n          }\n        }, serverConfig.auth, null);\n\n        // Session ID is returned in 'mcp-session-id' header from response\n        sessionId = response.sessionId || null;\n        initializeResponse = response;\n\n        if (sessionId) {\n          winston.debug(`MCP Server ${serverId} session initialized with ID: ${sessionId}`);\n        } else {\n          winston.debug(`MCP Server ${serverId} initialized (stateless, no session ID required)`);\n        }\n      } catch (initError) {\n        // If initialization fails, try anyway without session ID\n        // (some MCP servers don't require session ID)\n        winston.debug(`MCP Server ${serverId} initialization failed: ${initError.message}`);\n        throw initError;\n      }\n\n      // Save connection in cache with session ID\n      this.connections.set(serverId, {\n        config: serverConfig,\n        sessionId: sessionId,\n        initialized: true,\n        capabilities: initializeResponse.result?.capabilities || {}\n      });\n\n      winston.info(`MCP Server initialized: ${serverId}${sessionId ? ` (session: ${sessionId})` : ' (stateless)'}`);\n      return initializeResponse.result;\n    } catch (error) {\n      winston.error(`Error initializing MCP server ${serverId}:`, error);\n      throw error;\n    }\n  }\n\n  /**\n   * Gets the list of available tools from an MCP server\n   * @param {Object} serverConfig - MCP server configuration { url, projectId?, auth? }\n   * @returns {Promise<Array>} - List of available tools\n   */\n  async listTools(serverConfig) {\n    if (!serverConfig || !serverConfig.url) {\n      throw new Error('Server MCP configuration is missing or invalid');\n    }\n\n    const serverId = this.getCacheKey(serverConfig.url, serverConfig.projectId);\n\n    try {\n      // Check if server is already initialized\n      let connection = this.connections.get(serverId);\n      if (!connection) {\n        await this.initializeServer(serverConfig);\n        connection = this.connections.get(serverId);\n      }\n\n      const sessionId = connection?.sessionId || null;\n\n      // JSON-RPC call to get the list of tools\n      // If server requires session ID but we don't have it, try first without\n      let response;\n      try {\n        response = await this.sendJSONRPCRequest(serverConfig.url, {\n          jsonrpc: '2.0',\n          id: Date.now(),\n          method: 'tools/list',\n          params: {}\n        }, serverConfig.auth, sessionId);\n      } catch (error) {\n        // If it fails and we have a \"No valid session ID\" error, try without session ID\n        if (sessionId && error.message && error.message.includes('No valid session ID')) {\n          winston.debug(`Retrying tools/list without session ID for server ${serverId}...`);\n          response = await this.sendJSONRPCRequest(serverConfig.url, {\n            jsonrpc: '2.0',\n            id: Date.now(),\n            method: 'tools/list',\n            params: {}\n          }, serverConfig.auth, null);\n        } else {\n          throw error;\n        }\n      }\n\n      const tools = response.result?.tools || [];\n      winston.debug(`MCP Server ${serverId} returned ${tools.length} tools`);\n      return tools;\n    } catch (error) {\n      winston.error(`Error listing tools from MCP server ${serverId}:`, error);\n      throw error;\n    }\n  }\n\n\n  /**\n   * Removes a connection from cache\n   * @param {String} url - MCP server URL\n   * @param {String} projectId - Project ID (optional)\n   */\n  removeConnection(url, projectId) {\n    const serverId = this.getCacheKey(url, projectId);\n    this.connections.delete(serverId);\n  }\n\n  /**\n   * Clears all connections from cache\n   */\n  clearConnections() {\n    this.connections.clear();\n  }\n}\n\n// Singleton instance\nconst mcpService = new MCPService();\n\nmodule.exports = mcpService;\n\n"
  },
  {
    "path": "services/messageService.js",
    "content": "'use strict';\n\nvar Message = require(\"../models/message\");\nvar Project = require(\"../models/project\");\nvar MessageConstants = require(\"../models/messageConstants\");\nconst messageEvent = require('../event/messageEvent');\nconst messagePromiseEvent = require('../event/messagePromiseEvent');\nvar winston = require('../config/winston');\nvar cacheUtil = require(\"../utils/cacheUtil\");\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nconst fileUtils = require(\"../utils/fileUtils\");\nconst Integration = require(\"../models/integrations\");\nconst aiService = require(\"./aiService\");\n\nclass MessageService {\n\n    send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) {\n        return this.create(sender, senderFullname, recipient, text, id_project, createdBy, MessageConstants.CHAT_MESSAGE_STATUS.SENDING, attributes, type, metadata, language);\n    }\n\n    upsert(id, sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n        if (!id) {\n            return this.create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language);\n        } else {\n            winston.debug(\"Message upsert changeStatus:\" + status);\n            return this.changeStatus(id, status);\n        }\n    }\n\n    create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type, channel) {\n        let message = {\n            sender: sender,\n            senderFullname: senderFullname,\n            recipient: recipient,\n            text: text,\n            id_project: id_project,\n            createdBy: createdBy,\n            status: status,\n            attributes: attributes,\n            type: type,\n            metadata: metadata,\n            language: language,\n            channel_type: channel_type,\n            channel: channel\n        };\n        return this.save(message);\n    }\n\n    save(message) {\n\n        var that = this;\n        winston.debug('message.save called');\n\n        if (!message.createdAt) {\n            message.createdAt = new Date();\n        }\n\n        let sender = message.sender;\n        let senderFullname = message.senderFullname;\n        let recipient = message.recipient;\n        let recipientFullname = message.recipientFullname;\n        let text = message.text;\n        let id_project = message.id_project;\n        let createdBy = message.createdBy;\n        let status = message.status;\n        let attributes = message.attributes;\n        let type = message.type;\n        let metadata = message.metadata;\n        let language = message.language;\n        let channel_type = message.channel_type;\n        let channel = message.channel;\n\n\n\n        return new Promise(function (resolve, reject) {\n\n            //let q = Project.findOne({ _id: request.id_project, status: 100 });\n            // Continue quotes code here (see at requestService)\n\n            if (!createdBy) {\n                createdBy = sender;\n            }\n\n            var beforeMessage = {\n                sender: sender, senderFullname: senderFullname\n                , recipient: recipient, recipientFullname: recipientFullname\n                , text: text, id_project: id_project, createdBy: createdBy, status: status, attributes: attributes,\n                type: type, metadata: metadata, language: language, channel_type: channel_type, channel: channel\n            };\n\n            var messageToCreate = beforeMessage;\n            winston.debug('messageToCreate before', messageToCreate);\n            //   messageEvent.emit('message.create.simple.before', {beforeMessage:beforeMessage});\n\n\n\n            messagePromiseEvent.emit('message.create.simple.before', { beforeMessage: beforeMessage }).then(async (results) => {\n                winston.debug('message.create.simple.before results', results);\n                winston.debug('message.create.simple.before results prototype: ' + Object.prototype.toString.call(results));\n\n                if (results) {\n                    winston.debug('message.create.simple.before results.length: ' + results.length); //TODO ELIMINA DOPO CHE CREA CRASH\n                }\n\n                /*\n                if (results ) { //NN HA MAI FUNZIONATO. LA MADIFICA DEL VALORE AVVENIVA PER PUNTATORE\n                    winston.info('message.create.simple.before results.beforeMessage', results[0].beforeMessage);\n                    messageToCreate =  results[0].beforeMessage;\n                }\n                */\n\n                winston.debug('messageToCreate', messageToCreate);\n\n                if (messageToCreate.type === \"file\" && \n                    messageToCreate.metadata &&\n                    messageToCreate.metadata.type.startsWith('audio/')) {\n                        try {\n                            let audio_transcription = await that.getAudioTranscription(id_project, messageToCreate.metadata.src);\n                            if (audio_transcription) {\n                                messageToCreate.text = audio_transcription;\n                            }\n                        } catch(err) {\n                            winston.error(\"Error on getAudioTranscription: \", err);\n                        }\n                    }\n\n                // if (id_project) {\n\n                var newMessage = new Message({\n                    sender: messageToCreate.sender,\n                    senderFullname: messageToCreate.senderFullname,\n                    recipient: messageToCreate.recipient,\n                    recipientFullname: messageToCreate.recipientFullname, //for direct\n                    type: messageToCreate.type,\n                    text: messageToCreate.text,\n                    id_project: messageToCreate.id_project,\n                    createdBy: messageToCreate.createdBy,\n                    updatedBy: messageToCreate.createdBy,\n                    status: messageToCreate.status,\n                    metadata: messageToCreate.metadata,\n                    attributes: messageToCreate.attributes,\n                    language: messageToCreate.language,\n                    channel_type: messageToCreate.channel_type,\n                    channel: messageToCreate.channel\n                });\n\n                // winston.debug(\"create new message\", newMessage);\n\n                return newMessage.save(function (err, savedMessage) {\n                    if (err) {\n                        winston.error(\"Error saving the message\", { err: err, message: message, newMessage: newMessage });\n                        return reject(err);\n                    }\n                    winston.verbose(\"Message created\", savedMessage.toObject());\n\n                    messageEvent.emit('message.create.simple', savedMessage);\n                    that.emitMessage(savedMessage);\n\n                    let q = Project.findOne({ _id: message.id_project, status: 100 });\n                    if (cacheEnabler.project) {\n                        q.cache(cacheUtil.longTTL, \"projects:id:\" + message.id_project)  //project_cache\n                        winston.debug('project cache enabled for /project detail');\n                    }\n                    q.exec(async function (err, p) {\n                        if (err) {\n                            winston.error('Error getting project ', err);\n                        }\n                        if (!p) {\n                            winston.warn('Project not found ');\n                        }\n                        //TODO REMOVE settings from project\n                        let payload = {\n                            project: p,\n                            message: message\n                        }\n\n                        messageEvent.emit('message.create.quote', payload);\n                    });\n\n\n                    // if (savedMessage.status === MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED) {\n                    //     messageEvent.emit('message.received.simple', savedMessage);\n                    // }\n\n                    // if (savedMessage.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING) {\n                    //     messageEvent.emit('message.sending.simple', savedMessage);\n                    // }\n\n                    return resolve(savedMessage);\n                });\n\n\n\n            });\n\n\n\n\n        });\n\n    };\n\n    emitMessage(message) {\n        if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.RECEIVED) {\n            messageEvent.emit('message.received.simple', message);\n        }\n\n        if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENDING) {\n            messageEvent.emit('message.sending.simple', message);\n        }\n\n        if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.SENT) {\n            messageEvent.emit('message.sent.simple', message);\n        }\n\n        if (message.status === MessageConstants.CHAT_MESSAGE_STATUS.DELIVERED) {\n            messageEvent.emit('message.delivered.simple', message);\n        }\n    }\n\n    //   TODO must update also message.attributes from chat21\n    // attento già scatta su chat21handler\n\n    changeStatus(message_id, newstatus) {\n        winston.debug(\"changeStatus. \" + message_id + \" \" + newstatus);\n        var that = this;\n        return new Promise(function (resolve, reject) {\n            // winston.debug(\"request_id\", request_id);\n            // winston.debug(\"newstatus\", newstatus);\n\n            return Message.findByIdAndUpdate(message_id, { status: newstatus }, { new: true, upsert: false }, function (err, updatedMessage) {\n                if (err) {\n                    winston.error(err);\n                    return reject(err);\n                }\n                messageEvent.emit('message.update.simple', updatedMessage);\n                // winston.debug(\"updatedMessage\", updatedMessage);\n\n                that.emitMessage(updatedMessage);\n                return resolve(updatedMessage);\n            });\n        });\n\n    }\n\n    getTranscriptByRequestId(requestid, id_project) {\n        winston.debug(\"requestid\", requestid);\n        winston.debug(\"id_project\", id_project);\n        var that = this;\n        return new Promise(function (resolve, reject) {\n            return Message.find({ \"recipient\": requestid, id_project: id_project }).sort({ createdAt: 'asc' }).exec(function (err, messages) {\n                if (err) {\n                    winston.error(\"Error getting the transcript\", err);\n                    return reject(err);\n                }\n\n                winston.debug(\"messages\", messages);\n\n                if (!messages) {\n                    return resolve(messages);\n                }\n\n\n\n                var transcript = '';\n                // messages.forEach(message => {\n                for (var i = 0; i < messages.length; i++) {\n                    var message = messages[i];\n                    // winston.debug(\"message\", message);\n                    // winston.debug(\"message.createdAt\", message.createdAt);\n\n\n                    transcript = transcript +\n                        message.createdAt.toLocaleString('it', { timeZone: 'UTC' }) +\n                        ' ' + message.senderFullname +\n                        ': ' + message.text;\n\n                    //not add line break for last message\n                    if (i < messages.length - 1) {\n                        transcript = transcript + '\\r\\n';\n                    }\n\n                    // winston.debug(\"transcript\", transcript);\n                }\n                // });\n\n                // winston.debug(\"final transcript\", transcript);\n\n                // each message in messages\n                // p [#{message.createdAt.toLocaleString('it', { timeZone: 'UTC' })}] #{message.senderFullname}: #{message.text}\n                resolve(transcript);\n\n            });\n        });\n    }\n\n    getAudioTranscription(id_project, audio_url) {\n        return new Promise( async (resolve) => {\n            try {\n\n                if (process.env.NODE_ENV === 'test') {\n                    resolve(\"This is a mock trancripted audio\")\n                }\n\n                let file = await fileUtils.downloadFromUrl(audio_url);\n                let key;\n                let integration = await Integration.findOne({ id_project: id_project, name: 'openai' }).catch((err) => {\n                    winston.error(\"Error finding integration for openai\");\n                    resolve(null);\n\n                })\n\n                if (!integration || !integration?.value?.apikey) {\n                    winston.verbose(\"Integration for openai not found or apikey is undefined.\")\n                    key = process.env.GPTKEY;\n                } else {\n                    key = integration.value.apikey;\n                }\n\n                if (!key) {\n                    winston.verbose(\"No openai key provided\");\n                    resolve(null)\n                }\n\n                aiService.transcription(file, key).then((response) => {\n                    resolve(response.data.text);\n                }).catch((err) => {\n                    winston.error(\"Error getting audio transcription: \", err?.response?.data);\n                    resolve(null)\n                })\n            } catch(err) {\n                winston.error(\"Error on audio transcription: \", err)\n                resolve(null);\n            }\n        })\n    }\n\n}\n\n\nvar messageService = new MessageService();\nmodule.exports = messageService;"
  },
  {
    "path": "services/modulesManager.js",
    "content": "\nvar winston = require('../config/winston');\nvar validtoken = require('../middleware/valid-token');\nvar roleChecker = require('../middleware/has-role');\nvar passport = require('passport');\nrequire('../middleware/passport')(passport);\n\nconst MaskData = require(\"maskdata\");\n\nconst maskOptions = {\n  // Character to mask the data. default value is '*'\n  maskWith : \"*\",\n  // If the starting 'n' digits needs to be unmasked\n  // Default value is 4\n  unmaskedStartDigits : 40, //Should be positive Integer\n  //If the ending 'n' digits needs to be unmasked\n  // Default value is 1\n  unmaskedEndDigits : 40 // Should be positive Integer\n  };\n\n\nvar licenseKey = process.env.LICENSE_KEY;\n\nif (licenseKey) {\n    var maskedLicenseKey = MaskData.maskPhone(licenseKey, maskOptions);\n    winston.info(\"LicenseKey: \" + maskedLicenseKey);    \n    // winston.info(\"LicenseKey: \" + licenseKey);    \n}\n\nclass ModulesManager {\n\n    constructor() {       \n        this.stripe = undefined;\n        this.graphql = undefined;\n        this.elasticIndexer = undefined;      \n        this.facebookRoute = undefined;\n        this.jwthistoryArchiver = undefined;\n        this.jwthistoryRoute = undefined;\n        this.requestHistoryArchiver = undefined;\n        this.requestHistoryRoute = undefined;    \n        this.visitorCounterRoute = undefined;\n        this.visitorCounterMiddleware = undefined;\n        this.widgetsRoute = undefined;\n        this.enterprise = undefined;\n    }\n\n    injectBefore(app) {\n        winston.verbose(\"ModulesManager injectBefore\");\n        var res;\n        try {\n            // this.graphql = require('../../../modules/graphql/apollo-express');   \n            this.graphql = require('@tiledesk-ent/tiledesk-server-graphql').apolloExpress;\n           \n             res = this.graphql.injectBefore(app);     \n            winston.info(\"ModulesManager injectBefore graphql loaded\",res);\n          \n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager injectBefore graphql module not found\", err);\n            }else {\n                winston.error(\"ModulesManager error initializing graphql module\", err);\n            }\n            \n        }\n        return res;\n    }\n\n    injectAfter(httpServer,app,port) {\n        winston.verbose(\"ModulesManager inject\");\n        try {          \n            return this.graphql.injectAfter(httpServer,app,port);    \n            winston.info(\"ModulesManager injectAfter graphql loaded\");\n        } catch(err) {\n            winston.info(\"ModulesManager injectAfter graphql module not found\", err);\n        }\n    }\n\n    use(app) {\n        var that = this;\n        // winston.info(\"ModulesManager using controllers\");  \n\n        if (this.stripe) {\n            app.use('/modules/payments/stripe', this.stripe);\n            winston.info(\"ModulesManager stripe controller loaded\");       \n        }\n\n\n        if (this.jwthistoryRoute) {\n            app.use('/jwt/history', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken], this.jwthistoryRoute);\n            winston.info(\"ModulesManager jwthistory controller loaded\");       \n        }\n\n        \n    }\n    useUnderProjects(app) {\n        var that = this;\n        winston.debug(\"ModulesManager using controllers\");       \n            \n             \n    \n     \n        if (this.requestHistoryRoute) {\n            app.use('/:projectid/requests/:request_id/history', [passport.authenticate(['basic', 'jwt'], { session: false }), validtoken, roleChecker.hasRoleOrTypes(null, ['subscription'])] , this.requestHistoryRoute);\n            winston.info(\"ModulesManager requestHistory controller loaded\"); \n        }\n\n    \n\n\n        if (this.visitorCounterRoute) {     \n            app.use('/:projectid/visitorcounter', this.visitorCounterRoute);\n            winston.info(\"ModulesManager visitorcounter controller loaded\");       \n        }\n        // app.use( '/:projectid/widgets', require('@tiledesk-ent/tiledesk-server-visitorcounter').visitorCounterMiddleware);\n\n        if (this.widgetsRoute) {\n            this.widgetsRoute.use(this.visitorCounterMiddleware);\n            winston.info(\"ModulesManager visitorCounterMiddleware  loaded\");       \n            // console.log(\"8888\",this.visitorCounterMiddleware, this.widgetsRoute.stack)\n        }\n      \n\n        \n    }\n\n   \n    init(config) {\n        winston.debug(\"ModulesManager init\");\n\n\n        try {\n            this.enterprise = require('@tiledesk-ent/tiledesk-server-enterprise');\n            winston.debug(\"this.enterprise:\"+ this.enterprise);            \n            winston.info(\"ModulesManager enterprise initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init enterprise module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init enterprise module\", err);\n            }\n        }\n\n\n\n\n\n        try {\n            this.stripe = require('../pubmodules/s').stripeRoute;\n            // this.stripe = require('@tiledesk-ent/tiledesk-server-payments').stripeRoute;\n            winston.info(\"ModulesManager stripe initialized\");\n        } catch(err) {\n        //    if (err.code == 'MODULE_NOT_FOUND') {\n        //        winston.info(\"ModulesManager init stripe module not found\");\n        //    }else {\n                winston.error(\"ModulesManager error initializing init stripe module\", err);\n        //    }   \n        } \n\n\n\n        try {\n            this.jwthistoryArchiver = require('@tiledesk-ent/tiledesk-server-jwthistory').jwthistoryArchiver;\n            // this.jwthistoryArchiver.listen();\n            winston.debug(\"this.jwthistoryArchiver:\"+ this.jwthistoryArchiver);   \n            \n            this.jwthistoryRoute = require('@tiledesk-ent/tiledesk-server-jwthistory').jwthistoryRoute;\n            winston.debug(\"this.jwthistoryRoute:\"+ this.jwthistoryRoute);\n\n            winston.info(\"ModulesManager jwthistory initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init jwthistory module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init jwthistory module\", err);\n            }\n        }\n\n\n\n\n        try {\n            this.requestHistoryArchiver = require('@tiledesk-ent/tiledesk-server-request-history').listener;\n            // this.requestHistoryArchiver.listen();\n            winston.debug(\"this.requestHistoryArchiver:\"+ this.requestHistoryArchiver);   \n            \n            this.requestHistoryRoute = require('@tiledesk-ent/tiledesk-server-request-history').route;\n            winston.debug(\"this.requestHistoryRoute:\"+ this.requestHistoryRoute);\n\n            winston.info(\"ModulesManager requestHistory initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init requestHistory module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init requestHistory module\", err);\n            }\n        }\n\n\n        \n\n\n        // try {\n        //     this.dialogflowListener = require('@tiledesk-ent/tiledesk-server-dialogflow').listener;\n        //     // this.dialogflowListener.listen();\n        //     winston.debug(\"this.dialogflowListener:\"+ this.dialogflowListener);           \n\n        //     winston.info(\"ModulesManager dialogflow initialized\");\n        // } catch(err) {\n        //     if (err.code == 'MODULE_NOT_FOUND') {\n        //         winston.info(\"ModulesManager init dialogflow module not found\");\n        //     }else {\n        //         winston.error(\"ModulesManager error initializing init dialogflow module\", err);\n        //     }\n        // }        \n\n\n        \n        try {\n            this.visitorCounterRoute = require('@tiledesk-ent/tiledesk-server-visitorcounter').add(config.express);\n            winston.debug(\"this.visitorCounterRoute:\"+ this.visitorCounterRoute);        \n            this.visitorCounterMiddleware = require('@tiledesk-ent/tiledesk-server-visitorcounter').visitorCounterMiddleware;\n            winston.debug(\"this.visitorCounterMiddleware:\"+ this.visitorCounterMiddleware);        \n            this.widgetsRoute = config.routes.widgetsRoute;\n            winston.debug(\" this.widgetsRoute:\"+  this.widgetsRoute);        \n\n            winston.info(\"ModulesManager visitorCounter initialized\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init visitorCounter module not found\");\n            }else {\n                winston.error(\"ModulesManager error initializing init visitorCounter module\", err);\n            }\n        }\n\n\n\n\n        \n/*\n        try {\n           \n            this.facebookRoute = require('@tiledesk-ent/tiledesk-server-facebook-app').fbWebHook;\n            winston.debug(\"this.facebookRoute:\"+ this.facebookRoute);\n\n            winston.info(\"ModulesManager init facebook loaded\");\n        } catch(err) {\n            if (err.code == 'MODULE_NOT_FOUND') {\n                winston.info(\"ModulesManager init facebook module not found\");\n            }else {\n                winston.info(\"ModulesManager error initializing init facebook module\", err);\n            }\n        }\n*/\n\n        // try {\n        //     this.graphql = require('../modules/graphql/apollo');        \n        //     winston.info(\"ModulesManager init graphql loaded\");\n        // } catch(err) {\n        //     winston.info(\"ModulesManager init graphql module not found\", err);\n        // }\n\n\n        \n    }\n\n\n    start() {\n\n        \n        if (this.jwthistoryArchiver) {\n            try {\n                this.jwthistoryArchiver.listen();\n                winston.info(\"ModulesManager jwthistoryArchiver started\");\n            } catch(err) {        \n                winston.info(\"ModulesManager error starting jwthistoryArchiver module\", err);            \n            }\n        }\n\n        if (this.requestHistoryArchiver) {\n            try {\n                this.requestHistoryArchiver.listen();\n                winston.info(\"ModulesManager requestHistoryArchiver started\");\n            } catch(err) {        \n                winston.info(\"ModulesManager error starting requestHistoryArchiver module\", err);            \n            }\n        }\n\n        // if (this.dialogflowListener) {\n        //     try {\n        //         this.dialogflowListener.listen();\n        //         winston.info(\"ModulesManager dialogflowListener started\");\n        //     } catch(err) {        \n        //         winston.info(\"ModulesManager error starting dialogflowListener module\", err);            \n        //     }\n        // }\n\n\n        \n\n\n         \n    }\n\n\n    \n}\n\nvar modulesManager = new ModulesManager();\nmodule.exports = modulesManager;\n"
  },
  {
    "path": "services/mongoose-cache-fn.js",
    "content": "var winston = require('../config/winston');\n\n\nmodule.exports = function (mongoose, option) {\n    winston.info(\"Mongoose cache fn initialized\");\n   \n    mongoose.Query.prototype.cache = function (ttl, customKey) {\n        winston.debug(\"Mongoose cache fn cache called\");   \n        return this;\n    }      \n}"
  },
  {
    "path": "services/mongoose-cache.js",
    "content": "var winston = require('../config/winston');\n\n\nclass MongooseCache {\n\n    constructor(mongoose, option) {\n        winston.info(\"exec\");\n       \n        mongoose.Query.prototype.cache = function (ttl, customKey) {\n            console.log(\"redisssscache1\");       \n            return this;\n        }      \n    }\n\n}\n\n\n\n// var mongooseCache = new MongooseCache();\n// module.exports = mongooseCache;\n\nmodule.exports = MongooseCache;\n"
  },
  {
    "path": "services/operatingHoursService.js",
    "content": "'use strict';\n\nvar Project = require(\"../models/project\");\nvar moment_tz = require('moment-timezone');\nvar winston = require('../config/winston');\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\nclass OperatingHoursService {\n\n  projectIsOpenNow(projectId, callback) {\n\n    // winston.debug('O ---> [ OHS ] -> PROJECT ID ', projectId)\n    let q = Project.findOne({_id: projectId, status: 100});\n    if (cacheEnabler.project) { \n      q.cache(cacheUtil.longTTL, \"projects:id:\"+projectId)  //project_cache\n      winston.debug('project cache enabled for projectIsOpenNow');\n    }\n    q.exec(function (err, project) {\n      // winston.debug(\"XXXXXXXX project\", project);\n      if (err) {\n        winston.error(\"O ---> [ OHS ] -> ERROR GETTING PROJECT \", err);\n        // throw error\n        callback(null, { errorCode: 1000, msg: 'Error getting object.' });\n        return;\n      }\n      if (!project) {\n        winston.warn(\"OperatingHoursService projectIsOpenNow Project not found with id: \" + projectId);\n        // throw error\n        callback(null, { errorCode: 1010, msg: 'project not found for id', projectId });\n        return;\n      }\n\n\n\n      // evaluate if is open...\n\n      if (project) {\n\n        // IF THE TRIAL IS EXPIRED OR IF THE SUBSCIPTION IS NOT ACTIVE THE PROJECT IS ALWAYS OPEN EVEN IF activeOperatingHours IS SETTED TO true AND, FOR EXAMPLE,\n        // THE USER HAS SETTED ALL DAYS TO CLOSED\n\n                              //secondo me qui manca un parentesi tonda per gli or\n        if (project.profile && (project.profile.type === 'free' && project.trialExpired === true) || (project.profile.type === 'payment' && project.isActiveSubscription === false)) {\n          winston.debug('O ---> [ OHS ] -> trial Expired OR Subscription NOT Active - PROJECT ALWAYS OPEN') \n          callback(true, null) ;\n          return;\n        }\n\n\n        // winston.debug(\"[ O ] [ H ] [ S ] -> PROJECT FOUND: \", project);\n        // winston.debug(\"O ---> [ OHS ] -> PRJCT: IS ACTIVE OPERATING HOURS: \", project.activeOperatingHours);\n        // winston.debug(\"O ---> [ OHS ] -> PRJCT: OBJECT OPERATING HOURS: \", project.operatingHours);\n\n\n        // IF ACTIVE-OPERATING-HOURS \n        // - IS FALSE (activeOperatingHours is set to false when is created a new project) or \n        // - IS UNDEFINED (e.g.: for the project created before of the implementention of the property activeOperatingHours=false ) \n        // THE OPERATING-HOURS MUST NOT BE CONSIDERED AND THE AVAILABILITY WILL BE EVALUATED DIRECTLY ACCORDING TO \n        // AVAILABILITY OF THE USERS (THE PROJECT IS CONSIDERED OPEN 24 HOURS ON 24 AND SEVEN DAYS ON SEVEN)  \n        if (project.activeOperatingHours == false || project.activeOperatingHours == undefined ) {\n          // winston.debug('O ---> [ OHS ] -> OPERATING HOURS IS ACTIVE? -> ', project.activeOperatingHours)\n          // winston.debug('O ---> [ OHS ] -> * USE CASE 0 * OPERATING HOURS ARE NOT ACTIVE')\n          callback(true, null);\n        }\n\n        // operatingHours CAN NOT BE EMPTY - IT MUST AT LEAST CONTAIN THE TIMEZONE NAME\n        // POSSO CONSIDERARLO UN ERRORE (E DI CONSEGUENZA RITORNARE UN ERRORE) \n        // O COMUNQUE VALUTARE L'INTEZIONE DELL'UTENTE CHE HA ATTIVATO GLI ORARI DI APERTURA E CONSIDERARE IL PROGETTO COME CHIUSO\n        if (project.activeOperatingHours == true && project.operatingHours == '') {\n          // winston.debug('O ---> [ OHS ] -> OPERATING HOURS IS ACTIVE', project.activeOperatingHours, ' BUT OBJECT OPERATING HOURS IS EMPTY')\n          callback(null, { errorCode: 1020, msg: 'Operating hours is empty' });\n          return;\n        }\n\n        if (project.activeOperatingHours == true) {\n          // OPERATING HOURS IS ACTIVE - CHECK IF THE CURRENT TIME IS OUT OF THE TIME OF ACTIVITY\n          // winston.debug('O ---> [ OHS ] -> OPERATING HOURS IS ACTIVE? -> ', project.activeOperatingHours, ' - CHECK HOURS')\n\n          // PROJECT OPERATING HOURS \n          if (project.operatingHours) {\n            var operatingHours = project.operatingHours\n            var operatingHoursPars = JSON.parse(operatingHours)\n            // winston.debug('O -----> [ OHS ] -> OPERATING HOURS PARSED: ', operatingHoursPars);\n\n            var prjcTimezoneName = operatingHoursPars.tzname;\n\n            if (prjcTimezoneName == undefined || prjcTimezoneName == '' || prjcTimezoneName == null) {\n              // winston.debug('O ---> [ OHS ] -> PRJCT TIMEZONE NAME: ', prjcTimezoneName);\n              // callback('Timezone undefined.');\n              callback(null, { errorCode: 2000, msg: 'Timezone undefined.' });\n              return;\n              // return res.status(500).send({ success: false, msg: 'Timezone undefined.' });\n\n            } else {\n              // winston.debug('O ---> [ OHS ] -> PRJCT TIMEZONE NAME: ', prjcTimezoneName);\n\n              // ==============================================================================\n              //   ===================== * PROJECT TIMEZONE IS DEFINED * ====================\n              // ==============================================================================\n\n              // 1) ADD or SUBSTRACT PRJCT TZ FROM DATE NOW (is the date @ UTC 0) TO OBTAIN:\n              //    THE CURRENT DATE @ THE PROJECT TZ   \n              try {\n                var dateNowAtPrjctTz = addOrSubstractProjcTzOffsetFromDateNow(prjcTimezoneName);\n                // winston.debug('O ---> [ OHS ] -> *** CURRENT DATE @ THE PROJECT TZ ***', dateNowAtPrjctTz);\n\n                // FOR DEBUG (TO VIEW, IN DEBUG, THE NAME OF THE DAY INSTEAD OF THE NUMBER OF THE DAY)\n                var days = { '0': 'Sunday', '1': 'Monday', '2': 'Tuesday', '3': 'Wednesday', '4': 'Thursday', '5': 'Friday', '6': 'Saturday' };\n\n                // WEEK DAY @ THE PROJECT TZ (note: the week day is represented as a number) \n                var dayNowAtPrjctTz = dateNowAtPrjctTz.getDay();\n                // winston.debug('O ---> [ OHS ] -> *** DAY @ PRJCT TZ #', dayNowAtPrjctTz, '\"', days[dayNowAtPrjctTz], '\"');\n\n                // TRASFORM IN STRING THE DATE @ THE PRJCT TZ (IS THE DATE NOW TO WHICH I ADDED (OR SUBTRACT) THE TIMEZONE OFFSET (IN MS)\n                var dateNowAtPrjctTzToStr = dateNowAtPrjctTz.toISOString();\n\n                // GET THE NEW DATE NOE HOUR\n                var timeNowAtPrjctTz = dateNowAtPrjctTzToStr.substring(\n                  dateNowAtPrjctTzToStr.lastIndexOf(\"T\") + 1,\n                  dateNowAtPrjctTzToStr.lastIndexOf(\".\")\n                );\n                // winston.debug('O ---> [ OHS ] -> **** TIME @ PRJCT TZ ', timeNowAtPrjctTz);\n\n                var currentDayMatchesOneOfTheOpeningPrjctDays = checkDay(operatingHoursPars, dayNowAtPrjctTz);\n                // winston.debug('O ---> [ OHS ] -> THE DAY MATCHES: ', currentDayMatchesOneOfTheOpeningPrjctDays, ' !!!' );\n\n                if (currentDayMatchesOneOfTheOpeningPrjctDays == true) {\n\n\n                  var currentTimeIsBetweenOperatingTimes = checkTimes(operatingHoursPars, dayNowAtPrjctTz, timeNowAtPrjctTz);\n                  // winston.debug('O ---> [ OHS ] -> THE TIMES MATCHES', currentTimeIsBetweenOperatingTimes, ' !!!');\n                  if (currentTimeIsBetweenOperatingTimes == true) {\n\n                    // use case 1: THE CURRENT DAY (@ PRJCT TZ) MATCHES TO A DAY OF PRJCT OPERATING HOURS AND \n                    //             THE CURRENT TIME (@ PRJCT TZ) IS BETWEEN THE START TIME AND THE END TIME\n                    // winston.debug('O ---> [ OHS ] -> * USE CASE 1 * DAY & TIMES MATCHES')\n                    // findAvailableUsers(req.params.projectid);\n                    callback(true, null);\n\n                  } else {\n\n                    // use case 3: THE CURRENT DAY (@ PRJCT TZ) MATCHES TO A DAY OF PRJCT OPERATING HOURS BUT \n                    //             THE CURRENT TIME (@ PRJCT TZ) IS ! NOT BETWEEN THE START TIME AND THE END TIME\n                    // winston.debug('O ---> [ OHS ] -> * USE CASE 3 * DAY MATCHES BUT TIMES ! NOT')\n                    // user_available_array = [];\n                    // res.json(user_available_array);\n                    callback(false, null);\n                  }\n\n                } else {\n\n                  // use case 2: THE CURRENT DAY (@ PRJCT TZ) ! NOT MATCHES TO A DAY OF PRJCT OPERATING HOURS AND \n                  // winston.debug('O ---> [ OHS ] -> * USE CASE 2 * DAY ! NOT MATCHES')\n                  // user_available_array = [];\n                  // res.json(user_available_array);\n                  callback(false, null);\n                }\n\n              } catch (error) {\n\n                // winston.debug('O ---> [ OHS ] -> ERROR ADD/SUBSTRACT PRJCT TZ OFFSET FROM DATE NOW - ERROR: ', error)\n                // return res.status(500).send({ success: false, msg: error.message });\n                callback(null, { errorCode: 3000, msg: error.message });\n                return;\n              }\n\n            }\n          }\n        }\n      }\n\n    });\n\n\n  } // ./end projectIsOpenNow\n\n  slotIsOpenNow(projectId, slot_id, callback) {\n\n    let q = Project.findOne({ _id: projectId, status: 100});\n    if (cacheEnabler.project) { \n      q.cache(cacheUtil.longTTL, \"projects:id:\"+projectId)  //project_cache\n      winston.debug('project cache enabled for slotIsOpenNow');\n    }\n    q.exec( async (err, project) => {\n\n      if (err) {\n        winston.error(\"(slotIsOpenNow) Error getting project: \", err);\n        callback(null, { errorCode: 1000, msg: \"Error getting project.\" })\n        return;\n      }\n      if (!project) {\n        winston.warn(\"(slotIsOpenNow) Project not found with id: \" + projectId);\n        callback(null, { errorCode: 1010, msg: 'Project not found for id' + projectId });\n        return;\n      }\n\n      // Return ALWAYS true if the plan is free, trial is expired or the subscription is not active\n      if (project.profile && (project.profile.type === 'free' && project.trialExpired === true) || (project.profile.type === 'payment' && project.isActiveSubscription === false)) {\n        winston.debug('(slotIsOpenNow) Trial Expired or Subscription NOT Active') \n        callback(true, null) ;\n        return;\n      }\n\n      if (!project.timeSlots) {\n        winston.warn(\"(slotIsOpenNow) No time slots specified for the project \" + projectId);\n        callback(true, null)\n        return\n      }\n\n      let timeSlot = project.timeSlots[slot_id];\n\n      if (!timeSlot) {\n        callback(null, { errorCode: 1030, msg: 'Slot not found with id ' + slot_id })\n        return;\n      }\n\n      if (timeSlot.active == false) {\n        winston.verbose(\"(slotIsOpenNow) selected slot is not active\")\n        callback(true, null);\n        return;\n      }\n\n      if (!timeSlot.hours) {\n        callback(null, { errorCode: 1020, msg: 'Operating hours is empty' });\n        return;\n      }\n\n      const hours = JSON.parse(timeSlot.hours);\n      const tzname = hours.tzname;\n      delete hours.tzname;\n\n      // Get the current time in the specified timezone\n      const currentTime = moment_tz.tz(tzname);\n\n      const currentWeekday = currentTime.isoWeekday();\n      const daySlots = hours[currentWeekday];\n      if (!daySlots) {\n        callback(false, null)\n        return;\n      }\n\n      let promises = [];\n\n      daySlots.forEach((slot) => {\n        promises.push(slotCheck(currentTime, tzname, slot))\n      })\n  \n      await Promise.all(promises).then((resp) => {\n        if (resp.indexOf(true) != -1) {\n          callback(true, null);\n          return;\n        }\n        callback(false, null);\n        return;\n      })\n    })\n  }\n}\n\nfunction slotCheck(currentTime, tzname, slot) {\n  return new Promise((resolve) => {\n\n    const startTime = moment_tz.tz(slot.start, 'HH:mm', tzname);\n    const endTime = moment_tz.tz(slot.end, 'HH:mm', tzname);\n\n    if (currentTime.isBetween(startTime, endTime, null, '[)')) {\n      resolve(true)\n    } else {\n      resolve(false);\n    }\n  })\n}\n\nfunction addOrSubstractProjcTzOffsetFromDateNow(prjcTimezoneName) {\n  // DATE NOW UTC(0) IN MILLISECONDS\n  var dateNowMillSec = Date.now();\n  // winston.debug('O ---> [ OHS ] -> DATE NOW (UTC) in ms ', dateNowMillSec);\n\n  // CONVERT DATE NOW UTC(0) FROM MS IN DATE FORMAT\n  var dateNow = new Date(dateNowMillSec);\n  // winston.debug('O ---> [ OHS ] -> FOR DEBUG - DATE NOW (UTC): ', dateNow);\n\n  // =====================================================================\n  //  === GET TIMEZONE OFFSET USING * moment-timezone * library ===\n  // It returns, from the Area/Location passed, the offset in minutes of the Area/Location from UTC: \n  // > the minutes have NO sign if the timezone is after the UTC\n  // > the minutes have NEGATIVE sign if the timezone is before the UTC time \n  //       ... | -10 | -9 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 | UTC | +1 | +2 | +3 | +4 | +5 ...\n  //                                                  ↑    ↑    ↑    ↑    ↑    ↑           \n  //                here the minutes returned have negative sign  |  0  | here the minutes returned have no sign\n  //  (e.g. for the timezone America/New_York the offset is -240) |  0  | (e.g. for the timezone Europe/Rome the offset is 120)\n  //                                       \n  // =====================================================================\n\n  // ============>> using * moment-timezone * library\n  var offset = moment_tz.tz(moment_tz.utc(), prjcTimezoneName).utcOffset()\n  // winston.debug('O -----> [ OHS ] -> TIMEZONE OFFSET (USING MOMENT-TZ) ', offset);\n\n  // winston.debug('O ---> [ OHS ] -> Timezone IN  ', prjcTimezoneName, ' IS ', offset, ' TyPE OF ', typeof (offset));\n\n  if (offset < 0) {\n    // winston.debug('O ---> [ OHS ] -> THE SIGN OF THE OFFSET RETURNED IS NEGATIVE: THE TZ IS BEFORE UTC -> OFFSET ', offset);\n    var _offset = offset * -1\n    // winston.debug('O ---> [ OHS ] -> OFFSET CONVERTED ', _offset);\n    // winston.debug('O ---> [ OHS ] -> FOR DEBUG: ', prjcTimezoneName, 'is at \"UTC(-', _offset / 60, ')\"');\n    // TIMEZONE OFFSET DIRECTION +\n    var timezoneDirection = '-'\n    // winston.debug('O ---> [ OHS ] -> TIMEZONE OFFSET DIRECTION: ', timezoneDirection);\n\n    var prjcTzOffsetMillsec = _offset * 60000;\n    // winston.debug('O ---> [ OHS ] -> TIMEZONE OFFSET in MILLISECONDS: ', prjcTzOffsetMillsec);\n  } else {\n\n    // winston.debug('O ---> [ OHS ] -> THE SIGN OF THE OFFSET RETURNED IS ! NOT NEGATIVE: THE TZ IS AFTER UTC OR IS UTC -> OFFSET ', offset)\n    // winston.debug('O ---> [ OHS ] -> FOR DEBUG: ', prjcTimezoneName, 'is at \"UTC(+', offset / 60, ')\"');\n    var timezoneDirection = '+'\n    // winston.debug('O ---> [ OHS ] -> TIMEZONE OFFSET DIRECTION: ', timezoneDirection);\n\n    var prjcTzOffsetMillsec = offset * 60000;\n    // winston.debug('O ---> [ OHS ] -> TIMEZONE OFFSET in MILLISECONDS: ', prjcTzOffsetMillsec);\n  }\n\n\n  // https://stackoverflow.com/questions/5834318/are-variable-operators-possible\n  var operators = {\n    '+': function (dateNowMs, tzMs) { return dateNowMs + tzMs },\n    '-': function (dateNowMs, tzMs) { return dateNowMs - tzMs },\n  }\n\n  // ON THE BASIS OF 'TIMEZONE DIRECTION' ADDS OR SUBSTRATES THE 'TIMEZONE OFFSET' (IN MILLISECONDS) OF THE PROJECT TO THE 'DATE NOW' (IN MILLISECONDS)\n  var newDateNowMs = operators[timezoneDirection](dateNowMillSec, prjcTzOffsetMillsec)\n  // winston.debug('O ---> [ OHS ] -> DATE@PRJCT TZ (in ms):', newDateNowMs, ' IS = TO THE DATE NOW UTC ', dateNowMillSec, ' (in ms)', timezoneDirection, 'PRJC TZ OFFSET (in ms): ', prjcTzOffsetMillsec)\n\n  // TRANSFORM IN DATE THE DATE NOW (IN MILLSEC) TO WHICH I ADDED (OR SUBTRACT) THE TIMEZONE OFFSET (IN MS)\n  var newDateNow = new Date(newDateNowMs);\n\n  return newDateNow\n\n}\n\nfunction checkDay(operatingHoursPars, dayNowAtPrjctTz) {\n  // FOR DEBUG (TO VIEW, IN DEBUG, THE NAME OF THE DAY INSTEAD OF THE NUMBER OF THE DAY)\n  var days = { '0': 'Sunday', '1': 'Monday', '2': 'Tuesday', '3': 'Wednesday', '4': 'Thursday', '5': 'Friday', '6': 'Saturday' };\n  for (var operatingHoursweekDay in operatingHoursPars) {\n    if (operatingHoursweekDay != 'tzname') {\n      if (dayNowAtPrjctTz == operatingHoursweekDay) {\n        winston.debug('O ---> [ OHS ] -> CURRENT DAY \"', days[dayNowAtPrjctTz], '\" (CALCUCATE @THE PRJCT TZ) MATCHES TO THE PRJCT OPENING DAY \"', days[operatingHoursweekDay], '\"');\n\n        return true\n      }\n\n    }\n  }\n}\n\nfunction checkTimes(operatingHoursPars, dayNowAtPrjctTz, timeNowAtPrjctTz) {\n\n  for (var operatingHoursweekDay in operatingHoursPars) {\n    if (operatingHoursweekDay != 'tzname') {\n      if (dayNowAtPrjctTz == operatingHoursweekDay) {\n\n        // FOR DEBUG (TO VIEW, IN DEBUG, THE NAME OF THE DAY INSTEAD OF THE NUMBER OF THE DAY)\n        var days = { '0': 'Sunday', '1': 'Monday', '2': 'Tuesday', '3': 'Wednesday', '4': 'Thursday', '5': 'Friday', '6': 'Saturday' };\n\n        var result = false;\n        operatingHoursPars[operatingHoursweekDay].forEach(operatingHour => {\n          // winston.debug('O -----> [ OHS ] -> OPERATING HOUR ', operatingHour)\n          var startTime = operatingHour.start;\n          var endTime = operatingHour.end;\n          // winston.debug('CURRENT TIME (@THE PRJCT TZ) ', timeNowAtPrjctTz);\n          // winston.debug('O ---> [ OHS ] -> on', days[dayNowAtPrjctTz], 'the START OPERATING HOURS is AT: ', startTime);\n          // winston.debug('O ---> [ OHS ] -> on', days[dayNowAtPrjctTz], 'the END OPERATING HOURS is AT: ', endTime);\n\n          // MOMENT \n          var moment = require('moment');\n          // var currentTime = moment();\n          // winston.debug('MOMEMT CURRENT TIME ', currentTime)\n          var moment_currentTime = moment(timeNowAtPrjctTz, \"HH:mm\");\n          var moment_StartTime = moment(startTime, \"HH:mm\");\n          var moment_EndTime = moment(endTime, \"HH:mm\");\n          // winston.debug('MOMENT REQUEST TIME (@THE PROJECT UTC)', moment_newDateNow_hour);\n          // winston.debug('MOMENT START TIME ', moment_StartTime);\n          // winston.debug('MOMENT ./END TIME ', moment_EndTime);\n\n          var currentTimeIsBetween = moment_currentTime.isBetween(moment_StartTime, moment_EndTime);\n          // winston.debug('O ---> [ OHS ] -> CURRENT TIME', moment_currentTime, ' (@THE PRJCT TZ) IS BETWEEN OH-Start:', moment_StartTime, ' OH-End ', moment_EndTime, '->', currentTimeIsBetween);\n\n          if (currentTimeIsBetween == true) {\n            // USE CASE: THE OPERATING HOURS ARE ACTIVE AND THE DAY OF THE REQUEST MATCH WITH THE OPERATING HOURS WEEK-DAY\n            //           but the time of the request is outside the OPERATING hours\n            // winston.debug('O -----> [ OHS ] -> THE CURRENT TIME (@THE PRJCT TZ) MATCHES WITH THE OPERATING HOURS')\n\n            result = true;\n          }\n\n        });\n        return result;\n      }\n    }\n  }\n}\n\n\n\nvar operatingHoursService = new OperatingHoursService();\n\nmodule.exports = operatingHoursService;\n"
  },
  {
    "path": "services/pendingInvitationService.js",
    "content": "var express = require('express');\nvar PendingInvitation = require(\"../models/pending-invitation\");\nvar emailService = require(\"../services/emailService\");\nvar Project_user = require(\"../models/project_user\");\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\nvar RoleConstants = require(\"../models/roleConstants\");\n\nclass Pending_Invitation {\n\n  // USER NOT FOUND > SAVE IN PENDING INVITATION\n  saveInPendingInvitation(project_id, project_name, invited_user_email, invited_user_role, currentUserId, currentUserFirstname, currentUserLastname) {\n    return new Promise(function (resolve, reject) {\n      winston.debug('** ** SAVE IN PENDING INVITATION ** **');\n      // router.post('/', function (req, res) {\n\n      return PendingInvitation.find({ email: invited_user_email, id_project: project_id }, function (err, pendinginvitation) {\n        winston.debug('** ** FIND IN PENDING INVITATION ** ** ');\n\n        if (err) {\n          winston.error('** ** FIND IN PENDING INVITATION - ERROR ** **', err)\n          return reject({ success: false, msg: 'Error find object.', err: err });\n        }\n\n        if (!pendinginvitation.length) {\n\n          if (invited_user_role == '' || invited_user_role == undefined) {\n            winston.error('** ** FIND IN PENDING INVITATION - ROLE ERROR ', invited_user_role)\n            return reject({ success: false, msg: 'Role is required.', code: 405 });\n          }\n\n\n          winston.debug('** ** FIND IN PENDING INVITATION - OBJECT NOT FOUND -- SAVE IN PENDING INVITATION ** **')\n\n          // return reject({ success: false, msg: 'Object not found.' });\n          // console.log('** ** FOUND PENDING INVITATION ** ** ', pendinginvitation);\n\n          var newPendingInvitation = new PendingInvitation({\n            email: invited_user_email,\n            role: invited_user_role,\n            id_project: project_id,\n            createdBy: currentUserId,\n            updatedBy: currentUserId\n          });\n          return newPendingInvitation.save(function (err, savedPendingInvitation) {\n            if (err) {\n              winston.debug('** ** SAVE IN PENDING INVITATION ERROR ** **', err)\n              return reject({ success: false, msg: 'Error saving object.', err: err });\n            }\n            winston.debug('** ** SAVE IN PENDING INVITATION RESPONSE ** **', savedPendingInvitation)\n            emailService.sendInvitationEmail_UserNotRegistered(invited_user_email, currentUserFirstname, currentUserLastname, project_name, project_id, invited_user_role, savedPendingInvitation._id)\n\n            return resolve(savedPendingInvitation);\n\n          });\n\n        } else {\n\n          winston.debug('** ** FIND IN PENDING INVITATION - OBJECT FOUND ', pendinginvitation)\n          // emailService.sendInvitationEmail_UserNotRegistered(invited_user_email, currentUserFirstname, currentUserLastname, project_name, project_id, invited_user_role, pendinginvitation._id)\n\n          return reject({ success: false, msg: 'Pending Invitation already exist.', pendinginvitation });\n        }\n      });\n    });\n  };\n\n  checkNewUserInPendingInvitationAndSavePrcjUser(newUserEmail, newUserId) {\n    winston.debug('** ** CHECK NEW USER EMAIL ** **');\n    var that = this;\n    return new Promise(function (resolve, reject) {\n\n      return PendingInvitation.find({ email: newUserEmail }, function (err, pendinginvitations) {\n        if (err) {\n          winston.error('CHECK NEW USER EMAIL IN PENDING INVITATION ** ERROR ** ', err);\n          return reject({ msg: 'Error getting pending invitation' });\n        }\n        if (!pendinginvitations.length) {\n          winston.warn('CHECK NEW USER EMAIL IN PENDING INVITATION ** OBJECT NOT FOUND with email:  '+newUserEmail);\n          return resolve({ msg: 'New user email not found in pending invitation' });\n        }\n\n        winston.debug('** ** CHECK NEW USER EMAIL ** PENDING INVITATION FOUND ** SAVE A NEW PROJECT USER', pendinginvitations);\n\n        pendinginvitations.forEach(invite => {\n\n          winston.debug('** ** CHECK NEW USER EMAIL ** PENDING INVITATION FOUND ** PENDING INVITATION ROLE', invite.role);\n          winston.debug('** ** CHECK NEW USER EMAIL ** PENDING INVITATION FOUND ** PENDING INVITATION PRJCT ID', invite.id_project);\n\n          var newProject_user = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: invite.id_project,\n            id_user: newUserId,\n            role: invite.role,\n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: true,\n            createdBy: invite.createdBy,\n            updatedBy: invite.createdBy\n          });\n\n          return newProject_user.save(function (err, savedProject_user) {\n            if (err) {\n              winston.warn('--- > ERROR ', err)\n              return reject({ msg: 'Error saving project user.' });\n\n            }\n            that.removePendingInvitation(invite._id)\n\n            //cancella inviti pending\n            winston.debug('** ** CHECK NEW USER EMAIL ** PENDING INVITATION FOUND ** SAVED PROJECT USER', savedProject_user);\n            return resolve(savedProject_user);\n          });\n\n\n          // return resolve(pendinginvitation);\n        });\n      });\n      // });\n    });\n\n  }\n\n  removePendingInvitation(pendingInvitationId) {\n    winston.debug('DELETING PENDING INVITATION');\n    return new Promise(function (resolve, reject) {\n      return PendingInvitation.remove({ _id: pendingInvitationId }, function (err, pendinginvitation) {\n        if (err) {\n          winston.error('DELETING PENDING INVITATION - ERROR ', err);\n          return reject({ success: false, msg: 'Error deleting object.' });\n        }\n        // return resolve(pendinginvitation);\n      });\n    });\n  }\n\n\n\n}\nvar pending_invitation = new Pending_Invitation();\nmodule.exports = pending_invitation;\n"
  },
  {
    "path": "services/projectService.js",
    "content": "'use strict';\n\nvar Project = require(\"../models/project\");\nvar Project_user = require(\"../models/project_user\");\nvar mongoose = require('mongoose');\nvar departmentService = require('../services/departmentService');\nvar projectEvent = require(\"../event/projectEvent\");\nvar winston = require('../config/winston');\nconst cacheEnabler = require(\"./cacheEnabler\");\nconst cacheUtil = require(\"../utils/cacheUtil\");\nvar RoleConstants = require(\"../models/roleConstants\");\n\nclass ProjectService {\n\n  createAndReturnProjectAndProjectUser(name, createdBy, settings) {\n    return new Promise(function (resolve, reject) {\n\n    \n      var newProject = new Project({\n          name: name,\n          activeOperatingHours: false,\n          settings: settings,\n          createdBy: createdBy,\n          updatedBy: createdBy\n        });\n      \n        return newProject.save(function (err, savedProject) {\n          if (err) {\n            winston.error('Error saving the project ', err)\n            return reject({ success: false, msg: 'Error saving project.' });\n          }\n      \n          // PROJECT-USER POST\n          var newProject_user = new Project_user({\n            id_project: savedProject._id,\n            id_user: createdBy,\n            role: RoleConstants.OWNER,\n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: true,\n            createdBy: createdBy,\n            updatedBy: createdBy\n          });\n      \n         return  newProject_user.save(function (err, savedProject_user) {\n            if (err) {\n              winston.error('Error saving the projet_user ', err)\n              return reject(err);\n            }\n\n\n            return departmentService.createDefault(savedProject._id, createdBy).then(function(createdDepartment){\n              winston.verbose(\"Project created\", savedProject.toObject() );\n\n              projectEvent.emit('project.create', savedProject );\n              \n              return resolve({project:savedProject, project_user: savedProject_user});\n            });\n          });\n\n\n      });  \n\n  });\n\n  }\n\n  create(name, createdBy, settings) {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      return that.createAndReturnProjectAndProjectUser(name, createdBy, settings).then(function(projectAndProjectUser){\n        return resolve(projectAndProjectUser.project);\n      }).catch(function(err){\n        return reject(err);\n      });\n    });\n  }\n\n  async getCachedProject(id_project) {\n    return new Promise((resolve, reject) => {\n      let q = Project.findOne({ _id: id_project, status: 100 });\n      if (cacheEnabler.project) {\n        q.cache(cacheUtil.longTTL, \"projects:id:\" + id_project)  //project_cache\n        winston.debug('project cache enabled for /project detail');\n      }\n      q.exec( async (err, p) => {\n        if (err) {\n          reject(err);\n        }\n        if (!p) {\n          reject('Project not found')\n        }\n\n        resolve(p);\n      })\n    })\n  }\n\n\n}\n\nvar projectService = new ProjectService();\n\n\nmodule.exports = projectService;\n"
  },
  {
    "path": "services/projectUserService.js",
    "content": "'use strict';\n\nvar Project_user = require(\"../models/project_user\");\nvar winston = require('../config/winston');\n\nvar Role = require('../models/role');\nvar cacheUtil = require('../utils/cacheUtil');\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\nclass ProjectUserService {\n\n    checkAgentsAvailablesWithSmartAssignment(project, available_agents) {\n\n        let max_assigned_chat;\n        let available_agents_request = [];\n\n        if (project && project.settings && project.settings.chat_limit_on && project.settings.max_agent_assigned_chat) {\n            max_assigned_chat = project.settings.max_agent_assigned_chat;\n            winston.verbose('[ProjectUserService] max_agent_assigned_chat: ' + max_assigned_chat);\n        } else {\n            winston.verbose('[ProjectUserService] chat_limit_on or max_agent_assigned_chat is undefined');\n            return available_agents\n        }\n\n        if (available_agents.length == 0) {\n            return available_agents_request;\n        }\n\n        for (const aa of available_agents) {\n            let max_assigned_chat_specific_user = max_assigned_chat;\n            if (aa.max_assigned_chat && aa.max_assigned_chat != -1) {\n                max_assigned_chat_specific_user = aa.max_assigned_chat;\n            }\n            winston.verbose(\"[ProjectUserService] max_assigned_chat_specific_user \" + max_assigned_chat_specific_user);\n\n            if (aa.number_assigned_requests < max_assigned_chat_specific_user) {\n                available_agents_request.push(aa);\n            }\n        }\n\n        return available_agents_request;\n\n    }\n\n     async getWithPermissions(id_user, id_project, user_sub) {       \n        var project_user = await this.get(id_user, id_project, user_sub);\n        winston.debug(\"getWithPermissions for id_user \" + id_user + \" project_user \", project_user);\n\n        if (project_user) {\n            winston.debug(\"getWithPermissions project_user role: \" + project_user.role);\n\n            var role = await this.getPermissions(project_user.role, id_project);\n\n            if (role) {\n                project_user._doc.rolePermissions = role.permissions;   //https://github.com/Automattic/mongoose/issues/4614\n                // project_user.set(\"rolePermissions\", role);\n            }\n            \n\n        }\n        \n        return project_user;\n    }\n\n    async getPermissions(role, id_project) {\n         //               var cacheManager = require('../utils/cacheManager');\n        //               cacheManager.getClient().get(\"find in cache\")\n        let q = Role.findOne({\"name\":role, \"id_project\": id_project});\n\n        let cache_key = id_project+\":roles:\"+role;\n\n        if (cacheEnabler.role) { \n            q.cache(cacheUtil.defaultTTL, cache_key);\n            winston.debug(\"cacheEnabler.role enabled\");\n        }\n\n        var role = await q.exec();\n\n        if (role) \n            winston.debug(\"getWithPermissions role \", role.toJSON());\n\n        return role;\n\n    }\n\n    async get(id_user, id_project, user_sub) {\n        // JWT_HERE\n        var query = { id_project: id_project, id_user: id_user, status: \"active\"};\n        let cache_key = id_project+\":project_users:iduser:\"+id_user;\n\n        if (user_sub && (user_sub==\"userexternal\" || user_sub==\"guest\")) {\n            query = { id_project: id_project, uuid_user: id_user, status: \"active\"};\n            cache_key = id_project+\":project_users:uuid_user:\"+id_user;\n        }\n        winston.debug(\"hasRoleOrType query \" + JSON.stringify(query));\n\n        let q = Project_user.findOne(query);\n        if (cacheEnabler.project_user) { \n            q.cache(cacheUtil.defaultTTL, cache_key);\n            winston.debug(\"cacheEnabler.project_user enabled\");\n        }\n        var project_user = await q.exec();\n        winston.debug(\"ProjectUserUtil project_user\", project_user);\n       \n        return project_user;\n    }\n\n}\nvar projectUserService = new ProjectUserService();\n\n\nmodule.exports = projectUserService;\n"
  },
  {
    "path": "services/requestService.js",
    "content": "'use strict';\n\nvar departmentService = require('../services/departmentService');\nvar Request = require(\"../models/request\");\nvar Project_user = require(\"../models/project_user\");\nvar Project = require(\"../models/project\");\nvar messageService = require('../services/messageService');\nconst requestEvent = require('../event/requestEvent');\nconst leadEvent = require('../event/leadEvent');\nvar winston = require('../config/winston');\nvar RequestConstants = require(\"../models/requestConstants\");\nvar requestUtil = require(\"../utils/requestUtil\");\nvar cacheUtil = require(\"../utils/cacheUtil\");\nvar arrayUtil = require(\"../utils/arrayUtil\");\nvar cacheEnabler = require(\"../services/cacheEnabler\");\nvar UIDGenerator = require(\"../utils/UIDGenerator\");\nconst { TdCache } = require('../utils/TdCache');\nconst { QuoteManager } = require('./QuoteManager');\nvar configGlobal = require('../config/global');\nconst projectService = require('./projectService');\nconst axios = require(\"axios\").default;\n\nlet port = process.env.PORT || '3000';\nlet TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/ext/\";;\nif (process.env.TILEBOT_ENDPOINT) {\n    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/ext/\"\n}\n\nlet tdCache = new TdCache({\n    host: process.env.CACHE_REDIS_HOST,\n    port: process.env.CACHE_REDIS_PORT,\n    password: process.env.CACHE_REDIS_PASSWORD\n});\ntdCache.connect();\nlet qm = new QuoteManager({ tdCache: tdCache });\n\nclass RequestService {\n\n  constructor() {\n    this.listen();\n  }\n\n  listen() {\n    // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello \"Community bots Sendinblue Hubspot Qapla)\"\n    // this.updateSnapshotLead();\n    // this.sendMessageUpdateLead();\n  }\n\n      // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello \"Community bots Sendinblue Hubspot Qapla)\"\n  // updateSnapshotLead() {\n  //   leadEvent.on('lead.update', function (lead) {\n  //     setImmediate(() => {\n  //       winston.debug(\"updateSnapshotLead on lead.update \", lead);\n\n  //       Request.updateMany({ lead: lead._id, id_project: lead.id_project }, { \"$set\": { \"snapshot.lead\": lead } }, function (err, updates) {\n  //         if (err) {\n  //           winston.error(\"Error updating requests updateSnapshotLead\", err);\n  //           return 0;\n  //         }\n  //         winston.verbose(\"updateSnapshotLead updated for \" + updates.nModified + \" request\")\n  //         requestEvent.emit('request.update.snapshot.lead', { lead: lead, updates: updates });\n  //         return;\n  //       });\n  //       // Request.find({lead: lead._id, id_project: lead.id_project}, function(err, requests) {\n\n  //       //     if (err) {\n  //       //         winston.error(\"Error getting request by lead\", err);\n  //       //         return 0;\n  //       //     }\n  //       //     if (!requests || (requests && requests.length==0)) {\n  //       //         winston.warn(\"No request found for lead id \" +lead._id );\n  //       //         return 0;\n  //       //     }\n\n  //       //     requests.forEach(function(request) {\n\n\n  //       //     });\n\n  //       // });\n\n\n  //     });\n  //   });\n  // }\n\n\n  // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello \"Community bots Sendinblue Hubspot Qapla)\"\n  // sendMessageUpdateLead() {\n  //   leadEvent.on('lead.fullname.email.update', function (lead) {\n  //     winston.debug(\"lead.fullname.email.update \");\n  //     // leadEvent.on('lead.update', function(lead) {\n\n  //     setImmediate(() => {\n  //       winston.debug(\"sendMessageUpdateLead on lead.update \", lead);\n\n  //       Request.find({ lead: lead._id, id_project: lead.id_project }, function (err, requests) {\n\n  //         if (err) {\n  //           winston.error(\"Error getting sendMessageUpdateLead request by lead\", err);\n  //           return 0;\n  //         }\n  //         if (!requests || (requests && requests.length == 0)) {\n  //           winston.warn(\"sendMessageUpdateLead No request found for lead id \" + lead._id);\n  //           return 0;\n  //         }\n\n  //         // winston.info(\"sendMessageUpdateLead requests \", requests);\n\n  //         requests.forEach(function (request) {\n\n  //           winston.debug(\"sendMessageUpdateLead request \", request);\n\n  //           // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language)\n  //           messageService.send(\n  //             'system',\n  //             'Bot',\n  //             // lead.fullname,                                \n  //             request.request_id,\n  //             \"Lead updated\",\n  //             request.id_project,\n  //             'system',\n  //             {\n  //               subtype: \"info/support\",\n  //               \"updateconversation\": false,\n  //               messagelabel: { key: \"LEAD_UPDATED\" },\n  //               updateUserEmail: lead.email,\n  //               updateUserFullname: lead.fullname\n  //             },\n  //             undefined,\n  //             request.language\n\n  //           );\n\n  //         });\n\n  //       });\n\n  //     });\n  //   });\n  // }\n\n\n  getAvailableAgentsCount(agents) {\n\n    var project_users_available = agents.filter(function (projectUser) {\n      if (projectUser.user_available == true) {\n        return true;\n      }\n    });\n    winston.debug('++ AVAILABLE PROJECT USERS count ', project_users_available)\n\n    if (project_users_available && project_users_available.length > 0) {\n      return project_users_available.length;\n    } else {\n      return 0;\n    }\n\n\n  }\n\n  //change create with this\n  routeInternal(request, departmentid, id_project, nobot) {\n    var that = this;\n\n    return new Promise(function (resolve, reject) {\n\n      var context = { request: request };\n\n      // getOperators(departmentid, projectid, nobot, disableWebHookCall, context)\n      return departmentService.getOperators(departmentid, id_project, nobot, undefined, context).then(function (result) {\n\n        // winston.debug(\"getOperators\", result);\n\n        var assigned_at = undefined;\n\n        var status = RequestConstants.UNASSIGNED;\n        var assigned_operator_id;\n        var participants = [];\n        var participantsAgents = [];\n        var participantsBots = [];\n        var hasBot = false;\n\n        if (result.operators && result.operators.length > 0) {\n          assigned_operator_id = result.operators[0].id_user;\n\n          status = RequestConstants.ASSIGNED;\n\n          var assigned_operator_idString = assigned_operator_id.toString();\n          participants.push(assigned_operator_idString);\n\n          // botprefix\n          if (assigned_operator_idString.startsWith(\"bot_\")) {\n            hasBot = true;\n\n            // botprefix\n            var assigned_operator_idStringBot = assigned_operator_idString.replace(\"bot_\", \"\");\n            participantsBots.push(assigned_operator_idStringBot);\n          } else {\n            participantsAgents.push(assigned_operator_idString);\n            hasBot = false; //??\n          }\n\n          assigned_at = Date.now();\n        }\n        winston.debug(\"routeInternal assigned_operator_id: \" + assigned_operator_id);\n        winston.debug(\"routeInternal status: \" + status);\n\n\n\n        //  cosi modifica la request originale forse devi fare il clone?????\n        request.status = status;\n\n        request.participants = participants;\n        request.participantsAgents = participantsAgents;\n        request.participantsBots = participantsBots;\n        request.hasBot = hasBot;\n\n        request.department = result.department._id;\n        // request.agents = result.agents;\n        request.assigned_at = assigned_at;\n        request.waiting_time = undefined //reset waiting_time on reroute\n\n        //console.log(\"request.snapshot for \", request.request_id ,\" exists: \", request.snapshot ? \"yes\" : \"no\\n\", new Date());\n        //console.log(\"request.snapshot.agents for \", request.request_id ,\" exists: \", request.snapshot?.agents ? \"yes\" : \"no\\n\", new Date());\n        if (!request.snapshot) { //if used other methods than .create\n          request.snapshot = {}\n        }\n\n\n        request.snapshot.department = result.department;\n        request.snapshot.agents = result.agents;\n        request.snapshot.availableAgentsCount = that.getAvailableAgentsCount(result.agents);\n\n        return resolve(request);\n\n\n      }).catch(function (err) {\n        return reject(err);\n      });\n\n\n    });\n  }\n\n  // TODO  changePreflightByRequestId se un agente entra in request freflight true disabilitare add agente e reassing ma mettere un bottone removePreflight???\n  // usalo no_populate\n  async route(request_id, departmentid, id_project, nobot, no_populate) {\n\n    try {\n      winston.debug(\"request_id:\" + request_id);\n      winston.debug(\"departmentid:\" + departmentid);\n      winston.debug(\"id_project:\" + id_project);\n      winston.debug(\"nobot:\" + nobot);\n      //winston.info(\"main_flow_cache_3 route\");\n\n      // Find request\n      let query = Request.findOne({ request_id, id_project });\n      if (cacheEnabler.request) {\n        query = query.cache(cacheUtil.defaultTTL, id_project + \":requests:request_id:\" + request_id + \":simple\");\n        winston.debug('request cache enabled');\n      }\n      \n      const request = await query.exec();\n\n      if (!request) {\n        throw new Error(`Request not found: ${request_id}`);\n      }\n\n      // Clone before route\n      const requestBeforeRoute = request.toObject();\n      const beforeParticipants = requestBeforeRoute.participants;\n      const beforeDepartmentId = requestBeforeRoute.department?.toString();\n\n      // Route internal\n      const routedRequest = await this.routeInternal(request, departmentid, id_project, nobot);\n\n      const afterDepartmentId = routedRequest.department?.toString();\n\n      // Case 1 - No changes\n      if (\n        requestBeforeRoute.status === routedRequest.status &&\n        beforeDepartmentId === afterDepartmentId &&\n        requestUtil.arraysEqual(beforeParticipants, routedRequest.participants)\n      ) {\n\n        winston.verbose(`Request ${request.request_id} routed to same status/participants`);\n  \n        if (routedRequest.attributes?.fully_abandoned === true) {\n          request.status = RequestConstants.ABANDONED;\n          request.attributes.fully_abandoned = true;\n          request.markModified(\"status\");\n          request.markModified(\"attributes\");\n  \n          try {\n            await request.save();\n            winston.verbose(`Status set to ABANDONED for request ${request._id}`);\n          } catch (err) {\n            winston.error(\"Error updating request to ABANDONED\", err);\n          }\n        }\n\n        /**\n         * TODO: Restore proper functioning\n         * Option commented on 03/12/2025 by Johnny in order to allows clients to receive the request populated\n         * in such case of Abandoned Request (Status 150)\n        */\n        // if (no_populate === \"true\" || no_populate === true) {\n        //   winston.debug(\"no_populate is true\");\n        //   requestEvent.emit('request.update', request);\n        //   return request;\n        // }\n  \n        const requestComplete = await request\n          .populate(\"lead\")\n          .populate(\"department\")\n          .populate(\"participatingBots\")\n          .populate(\"participatingAgents\")\n          .populate({ path: \"requester\", populate: { path: \"id_user\" } })\n          .execPopulate();\n  \n        this.emitParticipantsEvents(\n          request,\n          requestComplete,\n          beforeParticipants\n        );\n  \n        return requestComplete;\n      }\n\n      \n      // Quota check\n      let project;\n      try {\n        project = await projectService.getCachedProject(id_project);\n      } catch (err) {\n        winston.warn(\"Error getting cached project, skip quota check\", err);\n      }\n\n      const isTestConversation = request.attributes?.sourcePage?.includes(\"td_draft=true\");\n      const isVoiceConversation = request.channel?.name === \"voice-vxml\";\n      const isStandardConversation = !isTestConversation && !isVoiceConversation;\n\n      if (isStandardConversation) {\n        const available = await qm.checkQuote(project, request, \"requests\");\n        if (!available) {\n          throw new Error(`Requests limits reached for project ${project._id}`);\n        }\n      }\n\n      // Case 2 - Leaving TEMP status. After internal routing: STATUS changed from 50 to 100 or 200\n      if (requestBeforeRoute.status === RequestConstants.TEMP &&\n        [RequestConstants.ASSIGNED, RequestConstants.UNASSIGNED].includes(routedRequest.status) &&\n        isStandardConversation\n      ) {\n        requestEvent.emit(\"request.create.quote\", { project, request });\n      }\n\n      // Case 3 - Conversation opened through proactive message. After internal routing: STATUS changed from undefined to 100\n      if (\n        !requestBeforeRoute.status &&\n        routedRequest.status === RequestConstants.ASSIGNED &&\n        isStandardConversation\n      ) {\n        requestEvent.emit(\"request.create.quote\", { project, request });\n      }\n\n      // Save and populate\n      const savedRequest = await routedRequest.save();\n\n      const requestComplete = await savedRequest\n        .populate(\"lead\")\n        .populate(\"department\")\n        .populate(\"participatingBots\")\n        .populate(\"participatingAgents\")\n        .populate({ path: \"requester\", populate: { path: \"id_user\" } })\n        .execPopulate();\n\n\n      this.emitParticipantsEvents(\n        request,\n        requestComplete,\n        beforeParticipants\n      );\n\n      requestEvent.emit(\"request.department.update\", requestComplete);\n\n      return requestComplete;\n\n    } catch (error) {\n      winston.error(\"Route error\", { error, request_id, id_project });\n      throw error;\n    }\n  }\n\n\n  reroute(request_id, id_project, nobot) {\n    var that = this;\n    var startDate = new Date();\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      let q = Request\n        .findOne({ request_id: request_id, id_project: id_project });\n\n      if (cacheEnabler.request) {\n        q.cache(cacheUtil.defaultTTL, id_project + \":requests:request_id:\" + request_id + \":simple\")      //request_cache\n        winston.debug('request cache enabled');\n      }\n\n      return q.exec(function (err, request) {\n\n        if (err) {\n          winston.error(err);\n          return reject(err);\n        }\n\n        winston.debug('request cache simple 3', request);\n\n        winston.debug(\"here reroute1 \");\n\n        //  Cannot read property 'toString' of undefined at /usr/src/app/services/requestService.js:404:61 at /usr/src/app/node_modules/cachegoose/out/extend-query.js:44:13 at Command.cb [as callback] (/usr/src/app/node_modules/cacheman-redis/node/index.js:142:9) at normal_reply (/usr/src/app/node_modules/redis/index.js:655:21) at RedisClient.return_reply (/usr/src/app/node_modules/redis/index.js:753:9) at JavascriptRedisParser.returnReply (/usr/src/app/node_modules/redis/index.js:138:18) at JavascriptRedisParser.execute (/usr/src/app/node_modules/redis-parser/lib/parser.js:544:14) at Socket.<anonymous> (/usr/src/app/node_modules/redis/index.js:219:27) at Socket.emit (events.js:314:20) at addChunk (_stream_readable.js:297:12) at readableAddChunk (_stream_readable.js:272:9) at Socket.Readable.push (_stream_readable.js:213:10) at TCP.onStreamRead (internal/stream_base_commons.js:188:23) {\"date\":\"Tue Mar 21 2023 18:45:47 GMT+0000 (Coordinated Unive\n        if (request.department === undefined) {\n          winston.error(\"Request with request_id \" + request_id + \"  has empty department. So I can't reroute\");\n          return reject(\"Request with request_id \" + request_id + \"  has empty department. So I can't reroute\");\n        }\n\n\n        return that.route(request_id, request.department.toString(), id_project, nobot).then(function (routedRequest) {\n\n          var endDate = new Date();\n          winston.verbose(\"Performance Request reroute in millis: \" + endDate - startDate);\n\n          return resolve(routedRequest);\n        }).catch(function (err) {\n          return reject(err);\n        });\n\n      });\n    });\n  }\n\n\n\n  createWithRequester(project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight) {\n\n    var request_id = 'support-group-' + id_project + \"-\" + UIDGenerator.generate();\n    winston.debug(\"request_id: \" + request_id);\n\n    return this.createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight);\n  }\n\n  createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) {\n\n    var request = {\n      request_id: request_id, project_user_id: project_user_id, lead_id: lead_id, id_project: id_project, first_text: first_text,\n      departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status, createdBy: createdBy,\n      attributes: attributes, subject: subject, preflight: preflight, channel: channel, location: location\n    };\n\n    return this.create(request);\n  };\n\n  async create(request) {\n    const createdAt = request.createdAt || new Date();\n\n    let {\n      request_id,\n      project_user_id,\n      lead_id,\n      id_project,\n      first_text,\n      sourcePage,\n      language,\n      userAgent,\n      status,\n      attributes,\n      subject,\n      preflight,\n      channel,\n      location,\n      participants = [],\n      tags,\n      notes,\n      priority,\n      auto_close,\n      followers,\n      contact\n    } = request;\n\n    let departmentid = request.departmentid || 'default';\n    let createdBy = request.createdBy || project_user_id || \"system\";\n\n    // Utils and flags\n    let payload;\n    let isTestConversation = false;\n    let isVoiceConversation = false;\n    let isStandardConversation = false;\n\n    const context = {\n      request: {\n        request_id, project_user_id, lead_id, id_project,\n        first_text, departmentid, sourcePage, language, userAgent, status,\n        createdBy, attributes, subject, preflight, channel, location,\n        participants,tags,notes,priority,auto_close,followers,contact\n      }\n    };\n\n    winston.debug(\"context: \", context);\n\n    // Initial assignment\n    let agents = [];\n    let participantsAgents = [];\n    let participantsBots = [];\n    let hasBot = false;\n    let dep_id;\n    let assigned_at;\n    let snapshot = {};\n\n    // Getting operators\n    let result;\n    try {\n      result = await departmentService.getOperators(departmentid, id_project, false, undefined, context);\n      winston.debug(\"Get operators result: \", result);\n    } catch (err) {\n      throw new Error(\"Error getting operators\", { cause: err });\n    }\n\n    agents = result.agents;\n\n    // Status and quote management\n    if (status === RequestConstants.TEMP) {\n      // Skip assignment\n      if (participants.length === 0) {\n        dep_id = result.department._id;\n      }\n    }\n    else {\n      \n      let project;\n      try {\n        project = await projectService.getCachedProject(id_project);\n      } catch (err) {\n        throw new Error(\"Error getting project\", { cause: err });\n      }\n\n      if (!project) {\n        winston.error(`Project not found for id_project: ${id_project}`);\n        throw new Error(`Project not found for id_project: ${id_project}`);\n      }\n\n      payload = { project, request };\n\n      // Test conversation\n      if (attributes?.sourcePage?.includes(\"td_draft=true\")) {\n        winston.verbose(\"is a test conversation --> skip quote availability check\");\n        isTestConversation = true;\n      }\n\n      // Voice conversation\n      else if (channel?.name === \"voice-vxml\") {\n        isVoiceConversation = true;\n        const available = await qm.checkQuote(project, request, \"voice_duration\");\n        if (!available) {\n          winston.info(`Voice duration limits reached for project ${id_project}`);\n          throw new Error(`Voice duration limits reached for project ${id_project}`);\n        }\n      }\n\n      // Standard conversation\n      else {\n        isStandardConversation = true;\n        const available = await qm.checkQuote(project, request, \"requests\");\n        if (!available) {\n          winston.info(`Requests limits reached for project ${project._id}`);\n          throw new Error(`Requests limits reached for project ${project._id}`);\n        }\n      }\n\n      // Assignment\n      if (participants.length === 0) {\n        if (result.operators?.length > 0) {\n          participants.push(result.operators[0].id_user.toString());\n        }\n        dep_id = result.department._id;\n      }\n\n      if (participants.length > 0) {\n        status = RequestConstants.ASSIGNED;\n\n        const firstParticipant = participants[0];\n\n        if (firstParticipant.startsWith(\"bot_\")) {\n          hasBot = true;\n          const botId = firstParticipant.replace(\"bot_\", \"\");\n          participantsBots.push(botId);\n        } else {\n          participantsAgents.push(firstParticipant);\n        }\n\n        assigned_at = Date.now();\n      } else {\n        status = RequestConstants.UNASSIGNED;\n      }\n    }\n\n    // Snapshot\n    if (dep_id) {\n      snapshot.department = result.department;\n    }\n    \n    snapshot.availableAgentsCount = this.getAvailableAgentsCount(agents);\n    \n    if (request.requester) {\n      snapshot.requester = request.requester;\n    }\n    if (request.lead) {\n      snapshot.lead = request.lead;\n    }\n\n    // Create request\n    const newRequest = new Request({\n      request_id,\n      requester: project_user_id,\n      lead: lead_id,\n      first_text,\n      subject,\n      status,\n      participants,\n      participantsAgents,\n      participantsBots,\n      hasBot,\n      department: dep_id,\n      sourcePage,\n      language,\n      userAgent,\n      assigned_at,\n      attributes,\n      id_project,\n      createdBy,\n      updatedBy: createdBy,\n      preflight,\n      channel,\n      location,\n      tags,\n      notes,\n      priority,\n      auto_close,\n      followers,\n      createdAt,\n      snapshot,\n      contact,\n    })\n\n    if (isTestConversation) {\n      newRequest.draft = true;\n    }\n\n    winston.debug('newRequest.', newRequest);\n    //winston.info(\"main_flow_cache_ requestService create\");\n\n    // Save request\n    try {\n      const savedRequest = await newRequest.save();\n      winston.debug(\"Request created\", savedRequest.toObject());\n\n      /**\n       * Se non faccio findOne usando la cache adesso, quando verrà eseguita la query in reroute() la request non ha il department.\n       */\n      let q = Request.findOne({ request_id, id_project });\n      if (cacheEnabler.request) {\n        q.cache(cacheUtil.defaultTTL, id_project + \":requests:request_id:\" + request_id + \":simple\");\n        winston.debug('request cache enabled');\n      }\n      await q.exec();\n\n      snapshot.agents = agents; \n      requestEvent.emit(\"request.create.simple\", savedRequest, snapshot);\n\n      if (isStandardConversation) {\n        requestEvent.emit(\"request.create.quote\", payload);\n      }\n\n      // Emit event to update snapshot in queue\n      // if (Object.keys(snapshot).length > 0) {\n      //   requestEvent.emit(\"request.snapshot.update\", {\n      //     request: savedRequest,\n      //     snapshot: snapshot\n      //   });\n      // }\n\n      return savedRequest;\n\n    } catch (err) {\n      winston.error(`RequestService error on save request ${newRequest.request_id}`, err);\n      throw new Error(\"RequestService error on save request\", { cause: err });\n    }\n\n  }\n\n  // DEPRECATED\n  // async create(request) {\n\n  //   if (!request.createdAt)  {\n  //     request.createdAt = new Date();\n  //   }\n\n  //   var request_id = request.request_id;\n  //   var project_user_id = request.project_user_id;\n  //   var lead_id = request.lead_id;\n  //   var id_project = request.id_project;\n  //   var first_text = request.first_text;\n  //   var departmentid = request.departmentid;\n  //   var sourcePage = request.sourcePage;\n  //   var language = request.language;\n  //   var userAgent = request.userAgent;\n  //   var status = request.status;\n  //   var createdBy = request.createdBy;\n  //   var attributes = request.attributes;\n  //   var subject = request.subject;\n  //   var preflight = request.preflight;\n  //   var channel = request.channel;\n  //   var location = request.location;\n  //   var participants = request.participants || [];\n  //   var tags = request.tags;\n  //   var notes = request.notes;\n  //   var priority = request.priority;\n  //   var auto_close = request.auto_close;\n  //   var followers = request.followers;\n  //   let createdAt = request.createdAt;\n\n  //   if (!departmentid) {\n  //     departmentid = 'default';\n  //   }\n\n  //   if (!createdBy) {\n  //     if (project_user_id) {\n  //       createdBy = project_user_id;\n  //     } else {\n  //       createdBy = \"system\";\n  //     }\n  //   }\n\n  //   // Utils\n  //   let payload;\n  //   let isTestConversation = false;\n  //   let isVoiceConversation = false;\n  //   let isStandardConversation = false;\n  //   var that = this;\n\n  //   return new Promise( async (resolve, reject) => {\n  //     var context = {\n  //       request: {\n  //         request_id: request_id, project_user_id: project_user_id, lead_id: lead_id, id_project: id_project,\n  //         first_text: first_text, departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status,\n  //         createdBy: createdBy, attributes: attributes, subject: subject, preflight: preflight, channel: channel, location: location,\n  //         participants: participants, tags: tags, notes: notes,\n  //         priority: priority, auto_close: auto_close, followers: followers\n  //       }\n  //     };\n  //     winston.debug(\"context\", context);\n\n  //     var participantsAgents = [];\n  //     var participantsBots = [];\n  //     var hasBot = false;\n  //     var dep_id = undefined;\n  //     var assigned_at = undefined;\n  //     var agents = [];\n  //     var snapshot = {};\n\n  //     try {\n  //       // (method) DepartmentService.getOperators(departmentid: any, projectid: any, nobot: any, disableWebHookCall: any, context: any): Promise<any>\n  //       var result = await departmentService.getOperators(departmentid, id_project, false, undefined, context);\n  //       winston.debug(\"getOperators\", result);\n\n  //     } catch (err) {\n  //       return reject(err);\n  //     }\n\n  //     agents = result.agents;\n\n  //     if (status == 50) {\n  //       // skip assignment\n  //       if (participants.length == 0) {\n  //         dep_id = result.department._id;\n  //       }\n  //     } else {\n\n  //       let project = await projectService.getCachedProject(id_project).catch((err) => {\n  //         winston.warn(\"Error getting cached project. Skip conversation quota check.\")\n  //         winston.warn(\"Getting cached project error:  \", err)\n  //       })\n\n  //       payload = {\n  //         project: project,\n  //         request: request\n  //       }\n\n  //       if (attributes && attributes.sourcePage && (attributes.sourcePage.indexOf(\"td_draft=true\") > -1)) {\n  //         winston.verbose(\"is a test conversation --> skip quote availability check\")\n  //         isTestConversation = true;\n  //       }\n  //       else if (channel && (channel.name === 'voice-vxml')) {\n  //         isVoiceConversation = true;\n  //         let available = await qm.checkQuote(project, request, 'voice_duration');\n  //         if (available === false) {\n  //           winston.info(\"Voice duration limits reached for project \" + project._id);\n  //           return reject(\"Voice duration limits reached for project \" + project._id);\n  //         }\n  //       }\n  //       else {\n  //         isStandardConversation = true;\n  //         let available = await qm.checkQuote(project, request, 'requests');\n  //         if (available === false) {\n  //           winston.info(\"Requests limits reached for project \" + project._id)\n  //           return reject(\"Requests limits reached for project \" + project._id);\n  //         }\n  //       }\n\n\n  //       if (participants.length == 0) {\n  //         if (result.operators && result.operators.length > 0) {\n  //           participants.push(result.operators[0].id_user.toString());\n  //         }\n  //         // for preflight it is important to save agents in req for trigger. try to optimize it\n  //         dep_id = result.department._id;\n  //       }\n\n  //       if (participants.length > 0) {\n  //         status = RequestConstants.ASSIGNED;\n  //         // botprefix\n  //         if (participants[0].startsWith(\"bot_\")) {\n\n  //           hasBot = true;\n  //           winston.debug(\"hasBot:\" + hasBot);\n\n  //           // botprefix\n  //           var assigned_operator_idStringBot = participants[0].replace(\"bot_\", \"\");\n  //           winston.debug(\"assigned_operator_idStringBot:\" + assigned_operator_idStringBot);\n\n  //           participantsBots.push(assigned_operator_idStringBot);\n\n  //         } else {\n\n  //           participantsAgents.push(participants[0]);\n\n  //         }\n\n  //         assigned_at = Date.now();\n\n  //       } else {\n  //         status = RequestConstants.UNASSIGNED;\n  //       }\n  //     }\n\n  //     if (dep_id) {\n  //       snapshot.department = result.department;\n  //     }\n\n  //     snapshot.agents = agents;\n  //     snapshot.availableAgentsCount = that.getAvailableAgentsCount(agents);\n\n  //     if (request.requester) {\n  //       snapshot.requester = request.requester;\n  //     }\n  //     if (request.lead) {\n  //       snapshot.lead = request.lead;\n  //     }\n\n  //     var newRequest = new Request({\n  //       request_id: request_id,\n  //       requester: project_user_id,\n  //       lead: lead_id,\n  //       first_text: first_text,\n  //       subject: subject,\n  //       status: status,\n  //       participants: participants,\n  //       participantsAgents: participantsAgents,\n  //       participantsBots: participantsBots,\n  //       hasBot: hasBot,\n  //       department: dep_id,\n  //       // agents: agents,                \n  //       //others\n  //       sourcePage: sourcePage,\n  //       language: language,\n  //       userAgent: userAgent,\n  //       assigned_at: assigned_at,\n  //       attributes: attributes,\n  //       //standard\n  //       id_project: id_project,\n  //       createdBy: createdBy,\n  //       updatedBy: createdBy,\n  //       preflight: preflight,\n  //       channel: channel,\n  //       location: location,\n  //       //snapshot: snapshot,\n  //       tags: tags,\n  //       notes: notes,\n  //       priority: priority,\n  //       auto_close: auto_close,\n  //       followers: followers,\n  //       createdAt: createdAt\n  //     });\n\n  //     if (isTestConversation) {\n  //       newRequest.draft = true;\n  //     }\n\n  //     winston.debug('newRequest.', newRequest);\n\n  //     //cacheinvalidation\n  //     return newRequest.save( async function (err, savedRequest) {\n\n  //       if (err) {\n  //         winston.error('RequestService error for method createWithIdAndRequester for newRequest' + JSON.stringify(newRequest), err);\n  //         return reject(err);\n  //       }\n  //       winston.debug(\"Request created\", savedRequest.toObject());\n\n  //       requestEvent.emit('request.create.simple', savedRequest);\n\n  //       if (isStandardConversation) {\n  //         requestEvent.emit('request.create.quote', payload);;\n  //       }\n\n  //       return resolve(savedRequest);\n\n  //     });\n  //   })\n  // }\n\n  \n  //DEPRECATED. USED ONLY IN SAME TESTS\n  createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {\n\n    // winston.debug(\"request_id\", request_id);\n\n\n    if (!departmentid) {\n      departmentid = 'default';\n    }\n\n    if (!createdBy) {\n      createdBy = requester_id;\n    }\n\n    var that = this;\n\n    return new Promise(function (resolve, reject) {\n\n      var context = {\n        request: {\n          request_id: request_id, requester_id: requester_id, id_project: id_project,\n          first_text: first_text, departmentid: departmentid, sourcePage: sourcePage, language: language, userAgent: userAgent, status: status,\n          createdBy: createdBy, attributes: attributes\n        }\n      };\n\n      // getOperators(departmentid, projectid, nobot, disableWebHookCall, context)\n\n      return departmentService.getOperators(departmentid, id_project, false, undefined, context).then(function (result) {\n\n        // winston.debug(\"getOperators\", result);\n\n        var status = RequestConstants.UNASSIGNED;\n        var assigned_operator_id;\n        var participants = [];\n        var participantsAgents = [];\n        var participantsBots = [];\n        var hasBot = false;\n\n        var assigned_at = undefined;\n        if (result.operators && result.operators.length > 0) {\n          assigned_operator_id = result.operators[0].id_user;\n          status = RequestConstants.ASSIGNED;\n\n          var assigned_operator_idString = assigned_operator_id.toString();\n          participants.push(assigned_operator_idString);\n\n          // botprefix\n          if (assigned_operator_idString.startsWith(\"bot_\")) {\n            hasBot = true;\n\n            // botprefix\n            var assigned_operator_idStringBot = assigned_operator_idString.replace(\"bot_\", \"\");\n            winston.debug(\"assigned_operator_idStringBot:\" + assigned_operator_idStringBot);\n            participantsBots.push(assigned_operator_idStringBot);\n\n          } else {\n            participantsAgents.push(assigned_operator_idString);\n          }\n          assigned_at = Date.now();\n        }\n        // winston.debug(\"assigned_operator_id\", assigned_operator_id);\n        // winston.debug(\"status\", status);\n\n        var newRequest = new Request({\n          request_id: request_id,\n          requester_id: requester_id,\n          first_text: first_text,\n          status: status,\n          participants: participants,\n          participantsAgents: participantsAgents,\n          participantsBots: participantsBots,\n          hasBot: hasBot,\n          department: result.department._id,\n          agents: result.agents,\n          //availableAgents: result.available_agents,\n\n          // assigned_operator_id:  result.assigned_operator_id,\n\n          //others\n          sourcePage: sourcePage,\n          language: language,\n          userAgent: userAgent,\n          assigned_at: assigned_at,\n          attributes: attributes,\n          //standard\n          id_project: id_project,\n          createdBy: createdBy,\n          updatedBy: createdBy\n        });\n\n\n        // winston.debug('newRequest.',newRequest);\n\n\n\n        //cacheinvalidation\n        return newRequest.save(function (err, savedRequest) {\n          if (err) {\n            winston.error('RequestService error for method createWithId for newRequest' + JSON.stringify(newRequest), err);\n            return reject(err);\n          }\n\n\n          winston.verbose(\"Request created\", savedRequest.toObject());\n\n\n          requestEvent.emit('request.create.simple', savedRequest);\n\n          return resolve(savedRequest);\n\n        });\n      }).catch(function (err) {\n        return reject(err);\n      });\n\n\n    });\n\n\n\n\n\n  }\n\n\n  changeStatusByRequestId(request_id, id_project, newstatus) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      //TODO CHECK IF ALREADY CLOSED\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { status: newstatus }, { new: true, upsert: false })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, updatedRequest) {\n\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n\n          requestEvent.emit('request.update', updatedRequest); //deprecated\n          requestEvent.emit(\"request.update.comment\", { comment: \"STATUS_CHANGE\", request: updatedRequest });//Deprecated\n          requestEvent.emit(\"request.updated\", { comment: \"STATUS_CHANGE\", request: updatedRequest, patch: { status: newstatus } });\n          //TODO emit request.clone or reopen also \n\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n  changeFirstTextAndPreflightByRequestId(request_id, id_project, first_text, preflight) {\n\n\n    return new Promise(function (resolve, reject) {\n      winston.debug(\"changeFirstTextAndPreflightByRequestId\" + request_id);\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      if (!first_text) {\n        winston.error(\"Error changing first text. The field first_text is empty for request \" + request_id);\n        return reject({ err: \"Error changing first text. The field first_text is empty\" });\n      }\n\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { first_text: first_text, preflight: preflight }, { new: true, upsert: false })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, updatedRequest) {\n\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n\n\n          requestEvent.emit('request.update.preflight', updatedRequest); //archive to audit log\n          requestEvent.emit('request.update', updatedRequest);\n          requestEvent.emit(\"request.update.comment\", { comment: \"FIRSTTEXT_PREFLIGHT_CHANGE\", request: updatedRequest });//Deprecated\n          requestEvent.emit(\"request.updated\", { comment: \"FIRSTTEXT_PREFLIGHT_CHANGE\", request: updatedRequest, patch: { first_text: first_text, preflight: preflight } });\n\n          //TODO emit request.clone or reopen also \n\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n  changeFirstTextByRequestId(request_id, id_project, first_text) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { first_text: first_text }, { new: true, upsert: false })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, updatedRequest) {\n\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n          requestEvent.emit('request.update', updatedRequest);\n          requestEvent.emit(\"request.update.comment\", { comment: \"FIRSTTEXT_CHANGE\", request: updatedRequest });//Deprecated\n          requestEvent.emit(\"request.updated\", { comment: \"FIRSTTEXT_CHANGE\", request: updatedRequest, patch: { first_text: first_text } });\n\n          //TODO emit request.clone or reopen also \n\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n\n\n  changePreflightByRequestId(request_id, id_project, preflight) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { preflight: preflight }, { new: true, upsert: false })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, updatedRequest) {\n\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n          requestEvent.emit('request.update', updatedRequest);\n          requestEvent.emit(\"request.update.comment\", { comment: \"PREFLIGHT_CHANGE\", request: updatedRequest });//Deprecated\n          requestEvent.emit(\"request.updated\", { comment: \"PREFLIGHT_CHANGE\", request: updatedRequest, patch: { preflight: preflight } });\n\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n  setClosedAtByRequestId(request_id, id_project, created_at, closed_at, closed_by) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      let duration = Math.abs(new Date(created_at) - new Date(closed_at))\n\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { closed_at: closed_at, closed_by: closed_by, duration: duration }, { new: true, upsert: false })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec( async function (err, updatedRequest) {\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n\n          let project = await projectService.getCachedProject(id_project).catch((err) => {\n            winston.warn(\"Error getting cached project. Skip conversation quota check.\")\n            winston.warn(\"Getting cached project error:  \", err)\n          })\n\n          let payload = {\n            project: project,\n            request: updatedRequest\n          }\n          if (updatedRequest.channel.name === 'voice-vxml') {\n            requestEvent.emit('request.close.quote', payload);\n          }\n          // winston.debug(\"updatedRequest\", updatedRequest);\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n\n\n  // unused\n  incrementMessagesCountByRequestId(request_id, id_project) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      return Request\n        .findOneAndUpdate({ request_id: request_id, id_project: id_project }, { $inc: { 'messages.messages_count': 1 } }, { new: true, upsert: false }, function (err, updatedRequest) {\n          if (err) {\n            winston.error(err);\n            return reject(err);\n          }\n          winston.debug(\"Message count +1\");\n          return resolve(updatedRequest);\n        });\n    });\n\n  }\n\n  updateWaitingTimeByRequestId(request_id, id_project, enable_populate) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"newstatus\", newstatus);\n\n      let q = Request\n        .findOne({ request_id: request_id, id_project: id_project });\n\n      if (enable_populate == true) {\n        winston.debug(\"updateWaitingTimeByRequestId  enable_populate\");\n\n        q.populate('lead')\n          .populate('department')\n          .populate('participatingBots')\n          .populate('participatingAgents')\n          .populate({ path: 'requester', populate: { path: 'id_user' } });\n      }\n\n\n      // if (cacheEnabler.request) {  //attention this cache is not usable bacause cacheoose don't support populate without .lean.. so if cached populated field is not returned with cacheoose, updateWaitingTime is only used in chat21webhook but i thik it is important for messages route\n      //   q.cache(cacheUtil.defaultTTL, id_project+\":requests:request_id:\"+request_id)           //request_cache\n      //   winston.debug('request cache enabled');\n      // }\n      q.exec(function (err, request) {\n        if (err) {\n          winston.error(err);\n          return reject(err);\n        }\n        //update waiting_time only the first time\n        if (!request.waiting_time) {\n          var now = Date.now();\n          var waitingTime = now - request.createdAt;\n          // winston.debug(\"waitingTime\", waitingTime);\n\n\n          request.waiting_time = waitingTime;\n          // TODO REENABLE SERVED\n          // request.status = RequestConstants.SERVED;\n          request.first_response_at = now;\n\n          // winston.debug(\" request\",  request);\n          winston.debug(\"Request  waitingTime setted\");\n          //cacheinvalidation\n          // return resolve(request.save());\n          request.save(function (err, savedRequest) {\n            if (err) {\n              return reject(err);\n            }\n            requestEvent.emit('request.update', savedRequest);\n\n            return resolve(savedRequest);\n          });\n\n\n\n        } else {\n          return resolve(request);\n        }\n\n      });\n\n    });\n\n  }\n\n\n  closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by, force) {\n\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n\n      if (force == undefined) {\n        winston.debug(\"force is undefined \");\n        force = false;\n      }\n      //  else {\n      //   winston.info(\"force is: \" + force);\n      //  }\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, request) {\n\n\n          if (err) {\n            winston.error(\"Error getting closing request with request_id: \" + request_id, err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error(\"Request not found for request_id \" + request_id + \" and id_project \" + id_project);\n            return reject({ \"success\": false, msg: \"Request not found for request_id \" + request_id + \" and id_project \" + id_project });\n          }\n          if (force == false && request.status == RequestConstants.CLOSED) {\n            // qui1000\n            //  if (request.statusObj.closed) {\n            requestEvent.emit('request.close', request);\n            winston.debug(\"Request already closed for request_id \" + request_id + \" and id_project \" + id_project);\n            return resolve(request);\n          }\n\n          winston.debug(\"sono qui\");\n\n          //  un utente può chiudere se appartiene a participatingAgents oppure meglio agents del progetto?\n\n\n          return that.changeStatusByRequestId(request_id, id_project, 1000).then(function (updatedRequest) {\n            //  qui1000\n            // return that.changeStatusByRequestId(request_id, id_project, {closed:true}).then(function(updatedRequest) {\n\n\n            // winston.debug(\"updatedRequest\", updatedRequest);\n            return messageService.getTranscriptByRequestId(request_id, id_project).then(function (transcript) {\n              // winston.debug(\"transcript\", transcript);\n              return that.updateTrascriptByRequestId(request_id, id_project, transcript).then(function (updatedRequest) {\n\n\n                if (skipStatsUpdate) {\n                  // TODO test it\n                  winston.verbose(\"Request closed with skipStatsUpdate and with id: \" + updatedRequest.id);\n                  winston.debug(\"Request closed \", updatedRequest);\n                  //TODO ?? requestEvent.emit('request.update', updatedRequest);\n                  requestEvent.emit('request.close', updatedRequest);\n                  requestEvent.emit('request.close.extended', { request: updatedRequest, notify: notify });\n                  return resolve(updatedRequest);\n                }\n\n                // setClosedAtByRequestId(request_id, id_project, closed_at, closed_by)\n                return that.setClosedAtByRequestId(request_id, id_project, request.createdAt, new Date().getTime(), closed_by).then(function (updatedRequest) {\n\n                  winston.verbose(\"Request closed with id: \" + updatedRequest.id);\n                  winston.debug(\"Request closed \", updatedRequest);\n                  //TODO ?? requestEvent.emit('request.update', updatedRequest);\n                  requestEvent.emit('request.close', updatedRequest);\n                  requestEvent.emit('request.close.extended', { request: updatedRequest, notify: notify });\n\n                  return resolve(updatedRequest);\n                });\n\n\n\n              });\n            });\n          }).catch(function (err) {\n            winston.error(err);\n            return reject(err);\n          });\n        });\n    });\n\n  }\n\n\n  reopenRequestByRequestId(request_id, id_project) {\n\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, request) {\n\n\n          if (err) {\n            winston.error(\"Error getting reopened request \", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error(\"Request not found for request_id \" + request_id + \" and id_project \" + id_project);\n            return reject({ \"success\": false, msg: \"Request not found for request_id \" + request_id + \" and id_project \" + id_project });\n          }\n\n          if (request.status == RequestConstants.ASSIGNED || request.status == RequestConstants.UNASSIGNED\n            // TODO REENABLE SERVED\n            // || request.status == RequestConstants.SERVED\n          ) {\n            winston.debug(\"request already open\");\n            return resolve(request);\n          }\n\n          if (request.participants.length > 0) {\n            request.status = RequestConstants.ASSIGNED;\n            // assigned_at?\n          } else {\n            request.status = RequestConstants.UNASSIGNED;\n          }\n          // TODO REENABLE SERVED\n          // attento served qui????forse no\n\n          //cacheinvalidation\n          request.save(function (err, savedRequest) {\n            if (err) {\n              winston.error(\"Error saving reopened the request\", err);\n              return reject(err);\n            }\n\n            requestEvent.emit('request.update', savedRequest);\n            requestEvent.emit(\"request.update.comment\", { comment: \"REOPEN\", request: savedRequest });//Deprecated\n            requestEvent.emit(\"request.updated\", { comment: \"REOPEN\", request: savedRequest, patch: { status: savedRequest.status } });\n\n            requestEvent.emit('request.reopen', savedRequest);\n\n            winston.verbose(\"Request reopened\", savedRequest);\n\n            // TODO allora neanche qui participatingAgent è ok? \n\n\n            return resolve(savedRequest);\n\n          });\n\n\n\n        })\n      // .catch(function(err)  {\n      //       winston.error(\"Error reopening the request\", err);\n      //       return reject(err);\n      //   });\n    });\n\n  }\n\n  updateTrascriptByRequestId(request_id, id_project, transcript) {\n\n    return new Promise(function (resolve, reject) {\n      // winston.debug(\"request_id\", request_id);\n      // winston.debug(\"transcript\", transcript);\n\n      //cacheinvalidation\n      return Request.findOneAndUpdate({ request_id: request_id, id_project: id_project }, { transcript: transcript }, { new: true, upsert: false }, function (err, updatedRequest) {\n        if (err) {\n          winston.error(err);\n          return reject(err);\n        }\n        // winston.debug(\"updatedRequest\", updatedRequest);\n        return resolve(updatedRequest);\n      });\n    });\n\n  }\n\n  findByRequestId(request_id, id_project) {\n    return new Promise(function (resolve, reject) {\n      return Request.findOne({ request_id: request_id, id_project: id_project }, function (err, request) {\n        if (err) {\n          return reject(err);\n        }\n        return resolve(request);\n      });\n    });\n  }\n\n\n\n\n  setParticipantsByRequestId(request_id, id_project, newparticipants) {\n\n    //TODO validate participants\n    // validate if array of string newparticipants\n    return new Promise(function (resolve, reject) {\n\n      var isArray = Array.isArray(newparticipants);\n\n      if (isArray == false) {\n        winston.error('setParticipantsByRequestId error  newparticipants is not an array for request_id ' + request_id + ' and id_project ' + id_project);\n        return reject('setParticipantsByRequestId error  newparticipants is not an array for request_id ' + request_id + ' and id_project ' + id_project);\n      }\n\n      return Request\n\n        .findOne({ request_id: request_id, id_project: id_project })\n        // qui cache ok\n        .exec(function (err, request) {\n          if (err) {\n            winston.error(\"Error setParticipantsByRequestId\", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          if (request.channel?.name === 'form' || request.channel?.name === 'email') {\n            if (Array.isArray(request.participantsAgents)) {\n              if (request.participantsAgents.length === 1) {\n                winston.error('Cannot add participants: participantsAgents already has one element for request_id ' + request_id + ' and id_project ' + id_project);\n                return reject('Cannot add participants: only one participant allowed for this request');\n              } else if (request.participantsAgents.length === 0) {\n                if (Array.isArray(newparticipants) && newparticipants.length === 1) {\n                  // ok, allow to add one participant\n                } else {\n                  winston.error('Can only add one participant for request_id ' + request_id + ' and id_project ' + id_project);\n                  return reject('Can only add one participant for this request');\n                }\n              }\n            }\n          }\n\n          var oldParticipants = request.participants;\n          winston.debug('oldParticipants', oldParticipants);\n          winston.debug('newparticipants', newparticipants);\n\n          if (requestUtil.arraysEqual(oldParticipants, newparticipants)) {\n            //if (oldParticipants === newparticipants) {\n            winston.verbose('Request members ' + oldParticipants + ' already equal to ' + newparticipants + ' for request_id ' + request_id + ' and id_project ' + id_project);\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n\n          }\n\n          request.participants = newparticipants;\n\n          var newparticipantsAgents = [];\n          var newparticipantsBots = [];\n\n\n          if (newparticipants && newparticipants.length > 0) {\n            var hasBot = false;\n            newparticipants.forEach(newparticipant => {\n              // botprefix\n              if (newparticipant.startsWith(\"bot_\")) {\n                hasBot = true;\n                // botprefix          \n                var assigned_operator_idStringBot = newparticipant.replace(\"bot_\", \"\");\n                winston.debug(\"assigned_operator_idStringBot:\" + assigned_operator_idStringBot);\n                newparticipantsBots.push(assigned_operator_idStringBot);\n\n              } else {\n                newparticipantsAgents.push(newparticipant);\n              }\n            });\n            request.hasBot = hasBot;\n          }\n\n          request.participantsAgents = newparticipantsAgents;\n          request.participantsBots = newparticipantsBots;\n\n\n\n\n\n          if (request.participants.length > 0) {\n            request.status = RequestConstants.ASSIGNED;\n            // assigned_at?\n          } else {\n            request.status = RequestConstants.UNASSIGNED;\n          }\n\n          request.waiting_time = undefined //reset waiting_time on reroute ????\n          // TODO REENABLE SERVED\n          // qui potrebbe essere che la richiesta era served con i vecchi agenti e poi facendo setParticipants si riporta ad assigned o unassigned perdendo l'informazione di served\n\n          //cacheinvalidation\n          return request.save(function (err, updatedRequest) {\n            // dopo save non aggiorna participating\n            if (err) {\n              winston.error(\"Error setParticipantsByRequestId\", err);\n              return reject(err);\n            }\n\n            return updatedRequest\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n\n\n                if (err) {\n                  winston.error(\"Error getting setParticipantsByRequestId\", err);\n                  return reject(err);\n                }\n\n                winston.debug(\"oldParticipants \", oldParticipants);\n\n                let newParticipants = requestComplete.participants;\n                winston.debug(\"newParticipants \", newParticipants);\n\n                var removedParticipants = oldParticipants.filter(d => !newParticipants.includes(d));\n                winston.debug(\"removedParticipants \", removedParticipants);\n\n                var addedParticipants = newParticipants.filter(d => !oldParticipants.includes(d));\n                winston.debug(\"addedParticipants \", addedParticipants);\n\n\n                requestEvent.emit('request.update', requestComplete);\n                requestEvent.emit(\"request.update.comment\", { comment: \"PARTICIPANTS_SET\", request: requestComplete });//Deprecated\n                requestEvent.emit(\"request.updated\", { comment: \"PARTICIPANTS_SET\", request: requestComplete, patch: { removedParticipants: removedParticipants, addedParticipants: addedParticipants } });\n\n                requestEvent.emit('request.participants.update', {\n                  beforeRequest: request,\n                  removedParticipants: removedParticipants,\n                  addedParticipants: addedParticipants,\n                  request: requestComplete\n                });\n\n                // TODO allora neanche qui participatingAgent è ok?\n                return resolve(requestComplete);\n              });\n          });\n\n        });\n\n\n    });\n  }\n\n  addParticipantByRequestId(request_id, id_project, member) {\n    winston.debug(\"request_id: \" + request_id);\n    winston.debug(\"id_project: \" + id_project);\n    winston.debug(\"addParticipantByRequestId member: \" + member);\n\n\n\n    //TODO control if member is a valid project_user of the project\n    // validate member is string\n    return new Promise(function (resolve, reject) {\n\n      if (member == undefined) {\n        var err = \"addParticipantByRequestId error, member field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        // qui cache         \n        .exec(function (err, request) {\n          if (err) {\n            winston.error(\"Error adding participant \", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          winston.debug(\"assigned_operator here1\");\n\n          // return Request.findById(id).then(function (request) {\n          if (request.participants.indexOf(member) == -1) {\n            request.participants.push(member);\n\n            // botprefix\n            if (member.startsWith(\"bot_\")) {\n              request.hasBot = true;\n\n              // botprefix\n              var assigned_operator_idStringBot = member.replace(\"bot_\", \"\");\n              winston.debug(\"assigned_operator_idStringBot:\" + assigned_operator_idStringBot);\n              request.participantsBots.push(assigned_operator_idStringBot);\n            } else {\n              request.participantsAgents.push(member);\n              request.hasBot = false; //???\n            }\n\n\n            if (request.participants.length > 0) {\n              request.status = RequestConstants.ASSIGNED;\n              var assigned_at = Date.now();\n              request.assigned_at = assigned_at;\n            } else {\n              request.status = RequestConstants.UNASSIGNED;\n            }\n            // check error here\n            //cacheinvalidation\n            request.save(function (err, savedRequest) {\n              if (err) {\n                winston.error(err);\n                return reject(err);\n              }\n\n              winston.debug(\"saved\", savedRequest);\n\n              return savedRequest\n                .populate('lead')\n                .populate('department')\n                .populate('participatingBots')\n                .populate('participatingAgents')\n                .populate({ path: 'requester', populate: { path: 'id_user' } })\n                .execPopulate(function (err, requestComplete) {\n\n                  if (err) {\n                    winston.error(\"Error getting addParticipantByRequestId\", err);\n                    return reject(err);\n                  }\n\n\n                  winston.debug(\"populated\", requestComplete);\n\n                  requestEvent.emit('request.update', requestComplete);\n                  requestEvent.emit(\"request.update.comment\", { comment: \"PARTICIPANT_ADD\", request: requestComplete });//Deprecated\n                  requestEvent.emit(\"request.updated\", { comment: \"PARTICIPANT_ADD\", request: requestComplete, patch: { member: member } });\n                  requestEvent.emit('request.participants.join', { member: member, request: requestComplete });\n\n                  return resolve(requestComplete);\n                });\n            });\n            // qui assignetat\n          } else {\n            winston.debug('Request member ' + member + ' already added for request_id ' + request_id + ' and id_project ' + id_project);\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n          }\n\n        });\n    });\n  }\n\n  removeParticipantByRequestId(request_id, id_project, member) {\n    winston.debug(\"request_id\", request_id);\n    winston.debug(\"id_project\", id_project);\n    winston.debug(\"member\", member);\n\n    return new Promise(function (resolve, reject) {\n\n\n\n      if (member == undefined) {\n        var err = \"removeParticipantByRequestId error, member field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        // .populate('participatingAgents')  //for abandoned_by_project_users\n        // qui cache    \n        .exec(async (err, request) => {\n\n          if (err) {\n            winston.error(\"Error removing participant \", err);\n            return reject(err);\n          }\n\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          var index = request.participants.indexOf(member);\n          // winston.debug(\"index\", index);\n\n          if (index > -1) {\n            request.participants.splice(index, 1);\n            // winston.debug(\" request.participants\",  request.participants);\n\n            // botprefix\n            if (member.startsWith(\"bot_\")) {\n              request.hasBot = false;\n              // botprefix\n              var assigned_operator_idStringBot = member.replace(\"bot_\", \"\");\n              winston.debug(\"assigned_operator_idStringBot:\" + assigned_operator_idStringBot);\n\n              var indexParticipantsBots = request.participantsBots.indexOf(assigned_operator_idStringBot);\n              request.participantsBots.splice(indexParticipantsBots, 1);\n            } else {\n              var indexParticipantsAgents = request.participantsAgents.indexOf(member);\n              request.participantsAgents.splice(indexParticipantsAgents, 1);\n\n\n\n              try {\n\n                //request.attributes.abandoned_by_project_users  start TODO move to routing-queue\n                if (!request.attributes) {\n                  winston.debug(\"removeParticipantByRequestId request.attributes is empty. creating it\");\n                  request.attributes = {};\n                }\n                if (!request.attributes.abandoned_by_project_users) {\n                  winston.debug(\"removeParticipantByRequestId request.attributes.abandoned_by_project_users is empty. creating it\");\n                  request.attributes.abandoned_by_project_users = {}\n                }\n\n                /*\n                winston.info(\"request.participatingAgents\",request.participatingAgents);\n                var pu = request.participatingAgents.find(projectUser => {\n                  console.log(projectUser);\n                  projectUser.id_user.toString() === member\n                });\n                winston.verbose(\"pu\",pu);\n                */\n\n                var pu = await Project_user.findOne({ id_user: member, id_project: id_project }).exec();\n                winston.debug(\"pu\", pu);\n\n                request.attributes.abandoned_by_project_users[pu._id] = new Date().getTime();\n                winston.debug(\"removeParticipantByRequestId request.attributes.abandoned_by_project_users\", request.attributes.abandoned_by_project_users);\n                //request.attributes.abandoned_by_project_users  end\n\n              } catch (e) {\n                winston.error(\"Error getting removeParticipantByRequestId pu\", e);\n              }\n\n\n            }\n\n\n            if (request.status != RequestConstants.CLOSED) {//don't change the status to 100 or 200 for closed request to resolve this bug. if the agent leave the group and after close the request the status became 100, but if the request is closed the state (1000) must not be changed\n              // qui1000 ????\n              if (request.participants.length > 0) {\n                request.status = RequestConstants.ASSIGNED;\n                // assignet_at?\n              } else {\n                request.status = RequestConstants.UNASSIGNED;\n              }\n            }\n\n            request.markModified('attributes');\n            // winston.debug(\" request\",  request);\n            //cacheinvalidation\n            return request.save(function (err, savedRequest) {\n              if (err) {\n                //winston.error(\"Error saving removed participant \", err);\n                return reject(err);\n              }\n\n              return savedRequest\n                .populate('lead')\n                .populate('department')\n                .populate('participatingBots')\n                .populate('participatingAgents')\n                .populate({ path: 'requester', populate: { path: 'id_user' } })\n                .execPopulate(function (err, requestComplete) {\n\n                  if (err) {\n                    winston.error(\"Error getting removed participant \", err);\n                    return reject(err);\n                  }\n\n\n                  requestEvent.emit('request.update', requestComplete);\n                  requestEvent.emit(\"request.update.comment\", { comment: \"PARTICIPANT_REMOVE\", request: requestComplete });//Deprecated\n                  requestEvent.emit(\"request.updated\", { comment: \"PARTICIPANT_REMOVE\", request: requestComplete, patch: { member: member } });\n                  requestEvent.emit('request.participants.leave', { member: member, request: requestComplete });\n\n\n                  return resolve(requestComplete);\n\n                });\n            });\n\n\n          } else {\n            winston.verbose('Request member ' + member + ' already not found for request_id ' + request_id + ' and id_project ' + id_project);\n\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n          }\n\n        });\n    });\n  }\n\n\n\n  updateAttributesByRequestId(request_id, id_project, attributes) {\n    var data = attributes;\n\n    Request.findOne({ \"request_id\": request_id, id_project: id_project })\n      .populate('lead')\n      .populate('department')\n      .populate('participatingBots')\n      .populate('participatingAgents')\n      .populate({ path: 'requester', populate: { path: 'id_user' } })\n      .exec(function (err, request) {\n        if (err) {\n          return reject(err);\n        }\n        if (!request) {\n          return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n        }\n\n\n        if (!request.attributes) {\n          winston.debug(\"empty attributes\")\n          request.attributes = {};\n        }\n\n        winston.debug(\" req attributes\", request.attributes)\n\n        Object.keys(data).forEach(function (key) {\n          var val = data[key];\n          winston.debug(\"data attributes \" + key + \" \" + val)\n          request.attributes[key] = val;\n        });\n\n        winston.debug(\" req attributes\", request.attributes)\n\n        // https://stackoverflow.com/questions/24054552/mongoose-not-saving-nested-object\n        request.markModified('attributes');\n\n        //cacheinvalidation\n        return request.save(function (err, savedRequest) {\n          if (err) {\n            winston.error(\"error saving request attributes\", err)\n            return reject({ msg: \"Error saving request attributes\", err: err });\n          }\n          winston.verbose(\" saved request attributes\", savedRequest.toObject())\n          requestEvent.emit(\"request.update\", savedRequest);\n          requestEvent.emit(\"request.update.comment\", { comment: \"ATTRIBUTES_UPDATE\", request: savedRequest });//Deprecated\n          requestEvent.emit(\"request.updated\", { comment: \"ATTRIBUTES_UPDATE\", request: savedRequest, patch: { attributes: data } });\n\n          requestEvent.emit(\"request.attributes.update\", savedRequest);\n          // allora neanche qui participatingAgent è ok?\n          return resolve(savedRequest);\n        });\n      });\n\n  }\n\n\n\n\n\n\n  addTagByRequestId(request_id, id_project, tag) {\n    // winston.debug(\"request_id\", request_id);\n    // winston.debug(\"id_project\", id_project);\n    // winston.debug(\"member\", member);\n\n    return new Promise(function (resolve, reject) {\n\n      if (tag == undefined) {\n        var err = \"addTagByRequestId error, tag field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, request) {\n          if (err) {\n            winston.error(\"Error adding tag \", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n\n          // return Request.findById(id).then(function (request) {\n          if (request.tags.indexOf(tag) == -1) {\n            request.tags.push(tag);\n            // check error here\n\n            //cacheinvalidation\n            request.save(function (err, savedRequest) {\n              if (err) {\n                winston.error(err);\n                return reject(err);\n              }\n\n              requestEvent.emit('request.update', savedRequest);\n              requestEvent.emit(\"request.update.comment\", { comment: \"TAG_ADD\", request: savedRequest });        //Deprecated\n              requestEvent.emit(\"request.updated\", { comment: \"TAG_ADD\", request: savedRequest, patch: { tags: tag } });\n\n\n              // allora neanche qui participatingAgent è ok?\n              return resolve(savedRequest);\n            });\n\n            // qui assignetat\n          } else {\n            winston.debug('Request tag ' + tag + ' already added for request_id ' + request_id + ' and id_project ' + id_project);\n            return resolve(request);\n          }\n        });\n    });\n  }\n\n\n  removeTagByRequestId(request_id, id_project, tag) {\n    // winston.debug(\"request_id\", request_id);\n    // winston.debug(\"id_project\", id_project);\n    // winston.debug(\"member\", member);\n\n    return new Promise(function (resolve, reject) {\n\n\n\n      if (tag == undefined) {\n        var err = \"removeTagByRequestId error, tag field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        .populate('lead')\n        .populate('department')\n        .populate('participatingBots')\n        .populate('participatingAgents')\n        .populate({ path: 'requester', populate: { path: 'id_user' } })\n        .exec(function (err, request) {\n\n          if (err) {\n            winston.error(\"Error removing tag \", err);\n            return reject(err);\n          }\n\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          // var index = request.tags.indexOf(tag);\n          var index = request.tags.findIndex(t => t.tag === tag);\n\n          winston.debug(\"index\", index);\n\n          if (index > -1) {\n            request.tags.splice(index, 1);\n            // winston.debug(\" request.participants\",  request.participants);    \n\n\n            //cacheinvalidation\n            request.save(function (err, savedRequest) {\n\n              if (!err) {\n                requestEvent.emit('request.update', savedRequest);\n                requestEvent.emit(\"request.update.comment\", { comment: \"TAG_REMOVE\", request: savedRequest });//Deprecated\n                requestEvent.emit(\"request.updated\", { comment: \"TAG_REMOVE\", request: savedRequest, patch: { tags: tag } });\n\n              }\n\n              // allora neanche qui participatingAgent è ok?\n\n              return resolve(savedRequest);\n\n            });\n\n\n          } else {\n            winston.info('Request tag ' + tag + ' already not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return resolve(request);\n          }\n\n\n        });\n    });\n  }\n\n\n\n\n\n\n\n\n  addFollowerByRequestId(request_id, id_project, member) {\n    winston.debug(\"request_id: \" + request_id);\n    winston.debug(\"id_project: \" + id_project);\n    winston.debug(\"addFollowerByRequestId member: \" + member);\n\n\n\n    //TODO control if member is a valid project_user of the project\n    // validate member is string\n    return new Promise(function (resolve, reject) {\n\n      if (member == undefined) {\n        var err = \"addFollowerByRequestId error, member field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        // qui cache         \n        .exec(function (err, request) {\n          if (err) {\n            winston.error(\"Error adding follower \", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          winston.debug(\"assigned_operator here1\");\n\n          // return Request.findById(id).then(function (request) {\n          if (request.followers.indexOf(member) == -1) {\n            request.followers.push(member);\n\n            request.save(function (err, savedRequest) {\n              if (err) {\n                winston.error(err);\n                return reject(err);\n              }\n\n              winston.debug(\"saved\", savedRequest);\n\n              return savedRequest\n                .populate('lead')\n                .populate('department')\n                .populate('participatingBots')\n                .populate('participatingAgents')\n                // .populate('followers')  \n                .populate({ path: 'requester', populate: { path: 'id_user' } })\n                .execPopulate(function (err, requestComplete) {\n\n                  if (err) {\n                    winston.error(\"Error getting addFollowerByRequestId\", err);\n                    return reject(err);\n                  }\n\n\n                  winston.debug(\"populated\", requestComplete);\n\n                  requestEvent.emit('request.update', requestComplete);\n                  requestEvent.emit(\"request.update.comment\", { comment: \"FOLLOWER_ADD\", request: requestComplete });//Deprecated\n                  requestEvent.emit(\"request.updated\", { comment: \"FOLLOWER_ADD\", request: requestComplete, patch: { member: member } });\n                  requestEvent.emit('request.followers.join', { member: member, request: requestComplete });\n\n                  return resolve(requestComplete);\n                });\n            });\n            // qui assignetat\n          } else {\n            winston.debug('Request member ' + member + ' already added for request_id ' + request_id + ' and id_project ' + id_project);\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              // .populate('followers')  \n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n          }\n\n        });\n    });\n  }\n\n\n\n\n\n  setFollowersByRequestId(request_id, id_project, newfollowers) {\n\n    //TODO validate participants\n    // validate if array of string newparticipants\n    return new Promise(function (resolve, reject) {\n\n      var isArray = Array.isArray(newfollowers);\n\n      if (isArray == false) {\n        winston.error('setFollowersByRequestId error  newfollowers is not an array for request_id ' + request_id + ' and id_project ' + id_project);\n        return reject('setFollowersByRequestId error  newfollowers is not an array for request_id ' + request_id + ' and id_project ' + id_project);\n      }\n\n      return Request\n\n        .findOne({ request_id: request_id, id_project: id_project })\n        // qui cache ok\n        .exec(function (err, request) {\n          if (err) {\n            winston.error(\"Error setFollowersByRequestId\", err);\n            return reject(err);\n          }\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n          var oldfollowers = request.followers;\n          winston.debug('oldParticipants', oldfollowers);\n          winston.debug('newparticipants', newfollowers);\n\n          if (requestUtil.arraysEqual(oldfollowers, newfollowers)) {\n            //if (oldParticipants === newparticipants) {\n            winston.verbose('Request members ' + oldfollowers + ' already equal to ' + newfollowers + ' for request_id ' + request_id + ' and id_project ' + id_project);\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n\n          }\n\n          request.followers = newfollowers;\n\n          //cacheinvalidation\n          return request.save(function (err, updatedRequest) {\n            // dopo save non aggiorna participating\n            if (err) {\n              winston.error(\"Error setFollowersByRequestId\", err);\n              return reject(err);\n            }\n\n            return updatedRequest\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n\n\n                if (err) {\n                  winston.error(\"Error getting setFollowersByRequestId\", err);\n                  return reject(err);\n                }\n\n                winston.debug(\"oldfollowers \", oldfollowers);\n\n                requestEvent.emit('request.update', requestComplete);\n                requestEvent.emit(\"request.update.comment\", { comment: \"FOLLOWERS_SET\", request: requestComplete });//Deprecated\n                requestEvent.emit(\"request.updated\", { comment: \"FOLLOWERS_SET\", request: requestComplete, patch: {} });\n\n                // requestEvent.emit('request.followers.update', {beforeRequest:request, \n                //             removedParticipants:removedParticipants, \n                //             addedParticipants:addedParticipants,\n                //             request:requestComplete});\n\n                return resolve(requestComplete);\n              });\n          });\n\n        });\n\n\n    });\n  }\n\n\n\n\n\n\n  removeFollowerByRequestId(request_id, id_project, member) {\n    winston.debug(\"request_id\", request_id);\n    winston.debug(\"id_project\", id_project);\n    winston.debug(\"member\", member);\n\n    return new Promise(function (resolve, reject) {\n\n\n\n      if (member == undefined) {\n        var err = \"removeFollowerByRequestId error, member field is null\";\n        winston.error(err);\n        return reject(err);\n      }\n\n\n      return Request\n        .findOne({ request_id: request_id, id_project: id_project })\n        // .populate('participatingAgents')  //for abandoned_by_project_users\n        // qui cache    \n        .exec(async (err, request) => {\n\n          if (err) {\n            winston.error(\"Error removing follower \", err);\n            return reject(err);\n          }\n\n          if (!request) {\n            winston.error('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n            return reject('Request not found for request_id ' + request_id + ' and id_project ' + id_project);\n          }\n\n          var index = request.followers.indexOf(member);\n          winston.debug(\"index\", index);\n\n          if (index > -1) {\n            request.followers.splice(index, 1);\n            // winston.debug(\" request.participants\",  request.participants);\n\n\n            // winston.debug(\" request\",  request);\n            //cacheinvalidation\n            return request.save(function (err, savedRequest) {\n              if (err) {\n                winston.error(\"Error saving removed follower \", err);\n                return reject(err);\n              }\n\n              return savedRequest\n                .populate('lead')\n                .populate('department')\n                .populate('participatingBots')\n                .populate('participatingAgents')\n                // .populate('followers')  \n                .populate({ path: 'requester', populate: { path: 'id_user' } })\n                .execPopulate(function (err, requestComplete) {\n\n                  if (err) {\n                    winston.error(\"Error getting removed follower \", err);\n                    return reject(err);\n                  }\n\n\n                  requestEvent.emit('request.update', requestComplete);\n                  requestEvent.emit(\"request.update.comment\", { comment: \"FOLLOWER_REMOVE\", request: requestComplete });//Deprecated\n                  requestEvent.emit(\"request.updated\", { comment: \"FOLLOWER_REMOVE\", request: requestComplete, patch: { member: member } });\n                  requestEvent.emit('request.followers.leave', { member: member, request: requestComplete });\n\n\n                  return resolve(requestComplete);\n\n                });\n            });\n\n\n          } else {\n            winston.verbose('Request member ' + member + ' already not found for request_id ' + request_id + ' and id_project ' + id_project);\n\n            return request\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              // .populate('followers')   \n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .execPopulate(function (err, requestComplete) {\n                return resolve(requestComplete);\n              });\n          }\n\n        });\n    });\n  }\n\n\n\n  async getRequestParametersFromChatbot(request_id) {\n\n    return new Promise( async (resolve, reject) => {\n      await axios({\n        url: TILEBOT_ENDPOINT + 'reserved/parameters/requests/' + request_id,\n        headers: {\n          'Content-Type': 'application/json'\n        },\n        method: 'GET'\n      }).then((response) => {\n        winston.debug(\"[RequestService] response: \", response);\n        resolve(response.data);\n      }).catch((err) => {\n        winston.error(\"get request parameter error:\", {\n          message: err.message,\n          data: err.response?.data\n        });\n        reject(err);\n      })\n    })\n\n  }\n\n  async getConversationsCount(id_project, status, preflight, hasBot, startDate, endDate) {\n    return new Promise( async (resolve, reject) => {\n      let query = { id_project: id_project, status: status, preflight: preflight, draft: { $in: [false, null] }};\n      if (hasBot != null) {\n        query.hasBot = hasBot;\n      }\n      if (status === 201) {\n        query.status = {\n          $in: [100,200]\n        }\n      }\n      if (preflight === null) {\n        delete query.preflight;\n      }\n      if (startDate && endDate) {\n        query.createdAt = { $gte: startDate.toDate(), $lte: endDate.toDate() }\n      }\n      winston.debug(\"getConversationsCount query: \", query)\n      let count = await Request.countDocuments(query).catch((err) => {\n        winston.error(\"Error getting requests count: \", err);\n        reject(err);\n      })\n      winston.verbose(\"Requests found for query \" + JSON.stringify(query) + \": \" + count);\n      resolve(count)\n    })\n  }\n\n  emitParticipantsEvents(beforeRequest, requestComplete, oldParticipants) {\n    const newParticipants = requestComplete.participants;\n  \n    const removedParticipants = oldParticipants.filter(\n      p => !newParticipants.includes(p)\n    );\n    const addedParticipants = newParticipants.filter(\n      p => !oldParticipants.includes(p)\n    );\n  \n    requestEvent.emit(\"request.update\", requestComplete);\n    requestEvent.emit(\"request.updated\", {\n      comment: \"REROUTE\",\n      request: requestComplete,\n      patch: { removedParticipants, addedParticipants }\n    });\n  \n    requestEvent.emit(\"request.participants.update\", {\n      beforeRequest,\n      removedParticipants,\n      addedParticipants,\n      request: requestComplete\n    });\n  }\n\n\n}\n\n\nvar requestService = new RequestService();\n\n\nmodule.exports = requestService;\n\n"
  },
  {
    "path": "services/schemaMigrationService.js",
    "content": "'use strict';\n\nvar path = require('path');\nvar fs = require('fs');\nvar winston = require('../config/winston');\nvar config = require('../config/database');\nvar migrateMongoose = require('migrate-mongoose');\n\nclass SchamaMigrationService {\n\n\n    isEmptyObject(obj) {\n\n      if (obj.length && obj.length > 0)\n          return false;          \n\n      if (obj.length === 0)\n        return true;           \n  }  \n\n    async checkSchemaMigration(currentSchamaVersion) {\n\n      if (process.env.DISABLE_AUTO_SHEMA_MIGRATION === \"true\") {\n        winston.info(\"SchemaMigration auto migrate schema is disabled\");\n        return;\n      }\n      \n      winston.info(\"SchemaMigration checking for schema updates.\");\n\n      // Define all your variables\n      var \n      //  migrationsDir = '/path/to/migrations/',\n      // templatePath,\n      migrationsPath = path.resolve(process.cwd(), 'migrations'),\n      dbUrl = process.env.DATABASE_URI || process.env.MONGODB_URI || config.database,\n      collectionName = 'schemaMigrations',\n      autosync = true;\n      \n      let migrator = new migrateMongoose({\n          migrationsPath: migrationsPath,\n          // templatePath: templatePath, // The template to use when creating migrations needs up and down functions exposed\n          dbConnectionUri: dbUrl, // mongo url\n          collectionName:  collectionName, // collection name to use for migrations (defaults to 'migrations')\n          autosync: autosync // if making a CLI app, set this to false to prompt the user, otherwise true\n      });\n      \n      var list = await migrator.list();\n      winston.debug(\"SchemaMigration script list\", list);\n\n      for (const script of list) {\n        winston.debug(\"script\", script);\n        const migrationFilePath = path.join(migrationsPath, script.filename);\n        if (!fs.existsSync(migrationFilePath)) {\n          winston.warn(\"SchemaMigration skipping migration (file not present): \" + script.filename + \" — remove the record from DB collection 'schemaMigrations' if not needed.\");\n          continue;\n        }\n        var runScript = await migrator.run('up', script.name);\n        if (runScript && !this.isEmptyObject(runScript)) {\n          winston.info(\"SchemaMigration script \" + script.name + \" executed.\");\n        }\n      } \n    // // Create a new migration\n    // migrator.create(migrationName).then(()=> {\n    //   console.log(`Migration created. Run `+ `mongoose-migrate up ${migrationName}`.cyan + ` to apply the migration.`);\n    // });\n    \n    // // Migrate Up\n    // promise = migrator.run('up', migrationName);\n    \n    // // Migrate Down\n    // promise = migrator.run('down', migrationName);\n    \n    // // List Migrations\n    // /*\n    // Example return val\n    \n    // Promise which resolves with\n    // [\n    //  { name: 'my-migration', filename: '149213223424_my-migration.js', state: 'up' },\n    //  { name: 'add-cows', filename: '149213223453_add-cows.js', state: 'down' }\n    // ]\n    \n    // */\n    // promise = migrator.list();\n    \n    \n    // // Prune extraneous migrations from file system\n    // promise = migrator.prune();\n    \n    // // Synchronize DB with latest migrations from file system\n    // /*\n    // Looks at the file system migrations and imports any migrations that are\n    // on the file system but missing in the database into the database\n    \n    // This functionality is opposite of prune()\n    // */\n    // var promise = await migrator.sync();\n    // console.log(\"SchemaMigration synched\", promise)\n    \n\n  \n  }\n    // async checkSchemaMigration(currentSchamaVersion) {\n    //   var migrationUpdateOperation = [\n    //     {\n    //       schemaVersion: 300000,\n    //       operation: `db.requests.update(\n    //         { preflight : { $exists: false }},\n    //         { $set: {\"preflighttest\": false} },\n    //         false,\n    //         true\n    //       )`\n    //     },\n    //     {\n    //       schemaVersion: 200000,\n    //       operation: `db.requests.update(\n    //         { preflight : { $exists: false }},\n    //         { $set: {\"preflighttest\": false} },\n    //         false,\n    //         true\n    //       )`\n    //     }\n    //   ];\n  \n    //   migrationUpdateOperation.sort((a, b) => a > b);\n  \n    //   winston.info(\"migrationUpdateOperation\", migrationUpdateOperation);\n  \n      \n  \n      \n    //     const db  = mongoose.connection;\n    //     winston.info(\"db\",db);\n  \n      \n    //     db.once('open', function callback () {\n  \n    //       migrationUpdateOperation.forEach(async(migOperation)=> {\n    //         try {\n    //           var operation = migOperation.operation;\n    //           winston.info(\"Schema migration operation:\"+ operation);\n    //           const result = await db.db.command(operation);\n    //           winston.info(\"Schema migration result\", result);\n    //         } catch(e) {\n    //           winston.error(\"Error during schema migration\", e);\n    //         }\n    //       });\n    //     });\n        \n    \n    \n     \n    //   // var bulk = Setting.collection.initializeUnorderedBulkOp();\n    //   //   bulk.find({<query>}).update({<update>});\n    //   //   bulk.find({<query2>}).update({<update2>});\n        \n    //   //   bulk.execute(function(err) {\n    //   //       ...\n    //   //   });\n  \n    // }\n\n}\n\nvar schamaMigrationService = new SchamaMigrationService();\n\n\nmodule.exports = schamaMigrationService;\n"
  },
  {
    "path": "services/settingDataLoader.js",
    "content": "'use strict';\n\nvar Setting = require(\"../models/setting\");\n\nvar winston = require('../config/winston');\n\nvar mongoose = require('mongoose');\n\nclass SettingDataLoader {\n\n\n  save() {\n    var that = this;\n    return new Promise(function (resolve, reject) {\n      var newSetting = new Setting({     \n      });\n    \n      return newSetting.save(function (err, savedSetting) {\n        if (err) {\n          if (err.code === 11000) { //error for dupes\n            winston.debug(\"duplicate setting\");\n          } else {\n           winston.error('Error saving the setting ', err);\n           return reject(err);\n          }\n        }\n        \n        winston.debug(\"setting saved\");\n        return resolve(savedSetting);\n      });\n    });\n\n  }\n\n\n\n}\n\n\nvar settingDataLoader = new SettingDataLoader();\n\n\nmodule.exports = settingDataLoader;\n"
  },
  {
    "path": "services/subscriptionNotifier.js",
    "content": "var Subscription = require('../models/subscription');\nvar SubscriptionLog = require('../models/subscriptionLog');\nconst requestEvent = require('../event/requestEvent');\nconst messageEvent = require('../event/messageEvent');\nconst message2Event = require('../event/message2Event');\nconst leadEvent = require('../event/leadEvent');\nconst botEvent = require('../event/botEvent');\nconst authEvent = require('../event/authEvent');\nconst departmentEvent = require('../event/departmentEvent');\nconst groupEvent = require('../event/groupEvent');\nconst faqBotEvent = require('../event/faqBotEvent');\nconst eventEvent = require('../pubmodules/events/eventEvent');\nconst event2Event = require('../pubmodules/events/event2Event');\nconst projectEvent = require('../event/projectEvent');\n\n// var Request = require(\"../models/request\");\nvar Message = require(\"../models/message\");\n// var Faq_kb = require(\"../models/faq_kb\");\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\nvar config = require('../config/database'); // get db config file\nvar cacheUtil = require(\"../utils/cacheUtil\");\n\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\nvar webhook_origin = process.env.WEBHOOK_ORIGIN || \"http://localhost:3000\";\nwinston.debug(\"webhook_origin: \"+webhook_origin);\n\n\nvar SUBSCRIPTION_LOG_ENABLED = false;\nif (process.env.SUBSCRIPTION_LOG_ENABLED==true || process.env.SUBSCRIPTION_LOG_ENABLED==\"true\") {\n  SUBSCRIPTION_LOG_ENABLED = true;\n}\nwinston.info(\"SUBSCRIPTION_LOG_ENABLED: \"+SUBSCRIPTION_LOG_ENABLED);\n\n\nvar request = require('retry-request', {\n  request: require('request')\n});\n\nclass SubscriptionNotifier {\n  // var SubscriptionNotifier = {\n\n   \n  findSubscriber(event, id_project) {\n    return new Promise(function (resolve, reject) {\n      let q = Subscription.find({event:event, $or:[{id_project: id_project}, {global: true}]});\n      if (cacheEnabler.subscription) {\n        q.cache(cacheUtil.longTTL, id_project+\":subscriptions:event:\"+event);  //CACHE_SUBSCRIPTION\n        winston.debug('subscription cache enabled');\n      }\n      \n      q.select(\"+secret +global\")\n      .exec(function (err, subscriptions) {\n        // if (subscriptions && subscriptions.length>0) {\n        //   winston.debug(\"Subscription.notify\", event, item , \"length\", subscriptions.length);\n        // }\n        resolve(subscriptions);\n      });\n    });\n  } \n\n  notify(subscriptions, payload, callback) {\n    // winston.debug(\"Subscription.notify\", event, item);\n    \n    // Subscription.find({event:event, id_project: item.id_project}).exec(function (err, subscriptions) {\n    //   if (subscriptions && subscriptions.length>0) {\n    //     winston.debug(\"Subscription.notify\", event, item , \"length\", subscriptions.length);\n    //   }\n\n    \n      //var json = {event: event, timestamp: Date.now(), payload: item};\n      winston.debug(\"subscriptions\",subscriptions);\n      var json = {timestamp: Date.now(), payload: payload};\n      subscriptions.forEach(function(s) {\n          \n        // winston.debug(\"s\",s);\n          var secret = s.secret;\n          var xHookSecret = secret;\n\n          let sJson = s.toObject();\n          delete sJson.secret;\n          delete sJson.global;\n          \n          json[\"hook\"] = sJson;\n\n    \n\n          var signOptions = {\n            issuer:  'https://tiledesk.com',\n            subject:  'subscription',        \n            audience:  'https://tiledesk.com/subscriptions/'+s._id,        \n          };\n\n          if (s.global==true){\n            signOptions.audience = 'https://tiledesk.com';\n\n            var alg = process.env.GLOBAL_SECRET_ALGORITHM;\n            if (alg) {\n              signOptions.algorithm = alg;\n            }\n            secret = process.env.GLOBAL_SECRET || config.secret;   \n            var pKey = process.env.GLOBAL_SECRET_OR_PRIVATE_KEY;\n            // console.log(\"pKey\",pKey);\n\n            if (pKey) {\n              secret = pKey.replace(/\\\\n/g, '\\n');\n            }\n\n          }\n    \n          var token = jwt.sign(sJson, secret, signOptions); //priv_jwt pp_jwt\n          json[\"token\"] = token;\n\n          winston.debug(\"Calling subscription \" + s.event + \" TO \" + s.target + \" with secret \" +secret+ \" with json \" , json);\n          // for sync use no retry \n          \n          request({\n            url: s.target,\n            headers: {\n             'Content-Type' : 'application/json',        \n              'x-hook-secret': xHookSecret,\n              'User-Agent': 'tiledesk-webhooks',\n              'Origin': webhook_origin\n            },\n            json: json,\n            method: 'POST'\n\n          }, function(err, response, jsonResponse){\n            winston.debug(\"subscription notifier SENT \" + s.event + \" TO \" + s.target +  \" with error \" , err);\n            winston.debug(\"SubscriptionLog response\", response);\n            winston.debug(\"SubscriptionLog jsonResponse\", jsonResponse);\n\n            if (SUBSCRIPTION_LOG_ENABLED==true) {\n              \n              var subscriptionLog = new SubscriptionLog({event: s.event, target: s.target, \n                response: JSON.stringify(response),\n                body: JSON.stringify(jsonResponse),\n                jsonRequest: JSON.stringify(json),\n                err: err, id_project:s.id_project});\n\n              subscriptionLog.save(function (errSL, sl) {           \n                if (errSL) {\n                  winston.error(\"Error saving subscriptionLog\", errSL);\n                  return 0;\n                }\n                winston.debug(\"SubscriptionLog saved\", sl);\n              });\n              \n            }\n            \n            if (err) {\n              winston.error(\"Error sending webhook for event \" + s.event + \" TO \" + s.target +  \" with error \" , err);\n              if (callback) {\n                callback(err, jsonResponse);\n              }\n              \n            }\n              //return\n              // console.log(\"callback qui1\", callback);\n            if (callback) {\n              // console.log(\"callback qui\", json);\n              callback(null, jsonResponse);\n            }\n\n          });\n      });\n  // });\n}\n  // https://mongoosejs.com/docs/middleware.html#post-async\n  decorate(model, modelName) {\n    var isNew = false;\n\n    model.pre('save', function(next) {\n      isNew = this.isNew;\n      winston.debug(\"Subscription.notify.pre (isNew)\", isNew);\n     \n      return next();\n    });\n\n    //winston.debug(\"decorate\");\n    // .afterCreate = function(item, next) {\n    model.post('save', function(doc, next) {\n     \n       // If we have isNew flag then it's an update\n       var event = (isNew) ? 'create' : 'update';\n       winston.debug(\"Subscription.notify.\"+event);\n      next(null, doc);\n      SubscriptionNotifier.notify(modelName+'.'+event, doc);\n    });\n\n    // model.afterUpdate = function(item, next) {\n    //   next(null, item);\n    //   SubscriptionNotifier.notify(modelName+'.update', item);\n    // }\n\n    // model.afterDestroy = function(next) {\n    //   next();\n    //   SubscriptionNotifier.notify(modelName+'.delete', {});\n    // }\n  }\n\n  start() {\n    winston.debug('SubscriptionNotifier start');\n\n    var enabled = process.env.RESTHOOK_ENABLED || \"false\";\n    winston.debug('SubscriptionNotifier enabled:'+enabled);\n\n    if (enabled===\"true\") {\n      winston.debug('SubscriptionNotifier enabled');\n    }else {\n      winston.info('Resthook disabled');\n      return 0;\n    }\n\n// queued \n    // var messageCreateKey = 'message.create';\n    // if (messageEvent.queueEnabled) {\n    //   messageCreateKey = 'message.create.queue';\n    // }\n    // messageEvent.on(messageCreateKey, function(message) {   //queued tested\n    //   setImmediate(() => {\n    //     winston.info('SubscriptionNotifier message.create');\n    //     subscriptionNotifier.subscribe('message.create', message);\n    //   });\n    // });\n\n    message2Event.on('message.create.**.channel.*', function(message) {    //notqueued high\n      // message2Event.on('message.create.request.channel.*', function(message) {\n      winston.debug(\"message2Event: \"+this.event, message);\n      subscriptionNotifier.subscribe(this.event, message);         \n    }, {async: true});\n\n\n\n    // messageEvent.on('message.received.for.bot', function(message) {\n    //   setImmediate(() => {\n    //     subscriptionNotifier.subscribe('message.received.for.bot', message);\n    //   });\n    // });\n\n    // messageEvent.on('message.received', function(message) {\n    //   setImmediate(() => {\n    //     subscriptionNotifier.subscribe('message.received', message);\n    //   });\n    // });\n    // messageEvent.on('message.sending', function(message) {\n    //   setImmediate(() => {\n    //     subscriptionNotifier.subscribe('message.sending', message);\n    //   });\n    // });\n\n\n\n// queued \n    // var requestCreateKey = 'request.create';\n    // if (requestEvent.queueEnabled) {\n    //   requestCreateKey = 'request.create.queue';\n    // }\n    // requestEvent.on(requestCreateKey, function(request) {  //queued tested\n    //   setImmediate(() => {\n    //     winston.info('SubscriptionNotifier request.create');\n    //     subscriptionNotifier.subscribe('request.create', request);       \n    //   });\n    // });\n\n\n// queued \n    // var requestUpdateKey = 'request.update';\n    // if (requestEvent.queueEnabled) {\n    //   requestUpdateKey = 'request.update.queue';\n    // }\n    // requestEvent.on(requestUpdateKey, function(request) {    //queued tested\n    //   setImmediate(() => {\n    //     winston.info('SubscriptionNotifier request.update');\n    //     subscriptionNotifier.subscribe('request.update', request);\n    //   });        \n    // });\n\n\n    // queued \n    // var requestCloseKey = 'request.close';   //request.close event here queued under job\n    // if (requestEvent.queueEnabled) {\n    //   requestCloseKey = 'request.close.queue';\n    // }\n    // requestEvent.on(requestCloseKey, function(request) {   //request.close event here noqueued     //queued tested\n    //   winston.info('SubscriptionNotifier request.close');\n    //   winston.info(\"request.close event here 1\")\n    //   setImmediate(() => {\n    //     Message.find({recipient:  request.request_id, id_project: request.id_project}).sort({updatedAt: 'asc'}).exec(function(err, messages) {\n    //       var requestJson = request;\n    //       if (request.toJSON) {\n    //         requestJson = request.toJSON();\n    //       }\n          \n    //       requestJson.messages = messages;\n    //       subscriptionNotifier.subscribe('request.close', requestJson);\n    //     });   \n    //   });\n    // });\n\n\n    // queued \n    // var leadCreateKey = 'lead.create';   //request.close event here queued under job\n    // if (leadEvent.queueEnabled) {\n    //   leadCreateKey = 'lead.create.queue';\n    // }\n    // leadEvent.on(leadCreateKey, function(lead) {    //notqueued high\n    //   setImmediate(() => {\n    //     subscriptionNotifier.subscribe('lead.create', lead);\n    //   });\n    // });\n\n\n      botEvent.on('faqbot.create', function(faqBot) {  //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faqbot.create', faqBot);\n        });\n      });\n\n      botEvent.on('faqbot.update', function(faqBot) {    //queued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faqbot.update', faqBot);\n        });\n      });\n\n      botEvent.on('faqbot.delete', function(faqBot) {    //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faqbot.delete', faqBot);\n        });\n      });\n\n      faqBotEvent.on('faq.create', function(faq) {       //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faq.create', faq);\n        });\n      });\n\n      faqBotEvent.on('faq.update', function(faq) {       //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faq.update', faq);\n        });\n      });\n      faqBotEvent.on('faq.delete', function(faq) {      //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('faq.delete', faq);\n        });\n      });\n\n      authEvent.on('user.signup',  function(event) {    //notqueued\n        setImmediate(() => {\n          var user = event.savedUser;\n          delete user.password;\n\n          subscriptionNotifier.subscribe('user.signup', user);         \n        });\n      });\n\n\n      // authEvent.emit('project_user.invite', {req:req, savedProject_userPopulated: savedProject_userPopulated});\n\n      authEvent.on('project_user.invite',  function(event) {   //notqueued\n        setImmediate(() => {\n          subscriptionNotifier.subscribe('project_user.invite', event.savedProject_userPopulated);         \n        });\n      });\n    \n      // authEvent.emit('project_user.update', {updatedProject_userPopulated:updatedProject_userPopulated, req: req});\n\n\n      // queued \n      // var authProjectUserUpdateKey = 'project_user.update';\n      // if (authEvent.queueEnabled) {\n      //   authProjectUserUpdateKey = 'project_user.update.queue';\n      // }\n      // authEvent.on(authProjectUserUpdateKey,  function(event) {    //notqueued  high\n      //   setImmediate(() => {\n      //     subscriptionNotifier.subscribe('project_user.update', event.updatedProject_userPopulated);         \n      //   });\n      // });\n    \n      // authEvent.emit('project_user.delete', {req: req, project_userPopulated: project_userPopulated});\n\n     authEvent.on('project_user.delete',  function(event) {      //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('project_user.delete', event.project_userPopulated);               \n      });\n     });\n\n\n     //TODO lanciare user.signin in questo modo uno esternamente con webhook può creare proactive greetings\n\n    departmentEvent.on('operator.select',  function(result) {     //notqueued\n      winston.debug(\"departmentEvent.on(operator.select\");\n\n      var operatorSelectedEvent = result.result;\n      var resolve = result.resolve;\n      var reject = result.reject;\n\n      // aggiungere context. lascio lo passso già a result.result\n      // operatorSelectedEvent[\"context\"] = result.context;\n\n      var disableWebHookCall = result.disableWebHookCall;\n      winston.debug(\"subscriptionNotifier disableWebHookCall: \"+ disableWebHookCall);\n\n      if (disableWebHookCall === true) {      \n        winston.debug(\"subscriptionNotifier disableWebHookCall enabled: \"+ disableWebHookCall);\n        return resolve(operatorSelectedEvent);\n      }\n      subscriptionNotifier.subscribe('operator.select', operatorSelectedEvent, function(err, json) {\n        winston.debug(\"qui callback\",json, err);\n        if (err) {\n          if (err.code == 404) {\n            winston.debug(\"call resolve default resolve because not found\", err);\n          }else {\n            winston.warn(\"call resolve default resolve because err\", err);\n          }\n          return resolve(operatorSelectedEvent);\n        }\n        if (json && json.operators) {\n          winston.verbose(\"call resolve valid json\", json);\n          return resolve(json);\n        }else {\n          winston.verbose(\"call resolve default resolve\");\n          return resolve(operatorSelectedEvent);\n        }\n\n      });         \n    });\n\n\n    departmentEvent.on('department.create',  function(department) {      //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('department.create', department);         \n      });\n    });\n  \n  \n    departmentEvent.on('department.update',  function(department) {       //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('department.update', department);         \n      });\n    });\n\n    departmentEvent.on('department.delete',  function(department) {        //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('department.delete', department);         \n      });\n    });\n\n\n    groupEvent.on('group.create',  function(group) {                      //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('group.create', group);         \n      });\n    });\n  \n  \n    groupEvent.on('group.update',  function(group) {                      //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('group.update', group);         \n      });\n    });\n\n    groupEvent.on('group.delete',  function(group) {                      //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('group.delete', group);         \n      });\n    });\n\n    eventEvent.on('event.emit',  function(event) {                        //notqueued\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('event.emit', event);         \n      });\n    });\n\n    // event2Event.on(name, savedEventPopulated);\n    event2Event.on('**', function(savedEventPopulated) {                //notqueued\n      setImmediate(() => {\n        winston.debug(\"eventname\",this.event);\n        subscriptionNotifier.subscribe('event.emit.'+this.event, savedEventPopulated);         \n      });\n    });\n\n\n    projectEvent.on('project.create',  function(project) {              //notqueued\n      setImmediate(() => {\n        var projectJson = project.toJSON();\n        projectJson.id_project = projectJson._id;\n        subscriptionNotifier.subscribe('project.create', projectJson);         \n      });\n    });\n\n    projectEvent.on('project.update',  function(project) {              //notqueued\n      setImmediate(() => {\n        var projectJson = project.toJSON();\n        projectJson.id_project = projectJson._id;\n        subscriptionNotifier.subscribe('project.update', projectJson);         \n      });\n    });\n\n    projectEvent.on('project.downgrade',  function(project) {           //notqueued\n      setImmediate(() => {\n        var projectJson = project.toJSON();\n        projectJson.id_project = projectJson._id;\n        subscriptionNotifier.subscribe('project.downgrade', projectJson);         \n      });\n    });\n\n    projectEvent.on('project.delete',  function(project) {              //notqueued\n      setImmediate(() => {\n        var projectJson = project.toJSON();\n        projectJson.id_project = projectJson._id;\n        subscriptionNotifier.subscribe('project.delete', projectJson);         \n      });\n    });\n\n    \n\n\n    // tagEvent.webhook for pypestream\n\n\n      // TODO add user events\n\n      winston.info('SubscriptionNotifier started');\n  }\n\n\n  subscribe(eventName, payload, callback ) {\n    winston.debug(\"Subscription.notify\");\n    // findSubscriber(event, id_project) \n    subscriptionNotifier.findSubscriber(eventName, payload.id_project).then(function(subscriptions) { \n      //winston.debug(\"Subscription.notify subscriptionNotifier\", subscriptions.length);\n      if (subscriptions && subscriptions.length>0) {\n        winston.debug(\"found Subscription.notify\", eventName, payload , \"length\", subscriptions.length);\n        subscriptionNotifier.notify(subscriptions, payload, callback);           \n      } else {\n        if (callback) {\n          var err = {msg:\"No subscriptions found\", code:404};\n          callback(err, undefined);\n        }\n      }\n    });\n\n  }\n\n\n\n};\n\nvar subscriptionNotifier = new SubscriptionNotifier();\n\n// winston.debug('messageEvent', messageEvent);\n\n\nmodule.exports = subscriptionNotifier;\n"
  },
  {
    "path": "services/subscriptionNotifierQueued.js",
    "content": "const requestEvent = require('../event/requestEvent');\nconst messageEvent = require('../event/messageEvent');\nconst leadEvent = require('../event/leadEvent');\nconst authEvent = require('../event/authEvent');\n\nvar Message = require(\"../models/message\");\nvar winston = require('../config/winston');\nvar subscriptionNotifier = require('../services/subscriptionNotifier');\n\nclass SubscriptionNotifierQueued {\n\n  start() {\n    winston.debug('SubscriptionNotifierQueued start');\n\n    var enabled = process.env.RESTHOOK_ENABLED || \"false\";\n    winston.debug('SubscriptionNotifierQueued enabled:'+enabled);\n\n    if (enabled===\"true\") {\n      winston.debug('SubscriptionNotifierQueued enabled');\n    }else {\n      winston.info('Resthook Queued disabled');\n      return 0;\n    }\n\n\n    var messageCreateKey = 'message.create';\n    if (messageEvent.queueEnabled) {\n      messageCreateKey = 'message.create.queue';\n    }\n    winston.debug('SubscriptionNotifierQueued messageCreateKey: ' + messageCreateKey);\n\n    messageEvent.on(messageCreateKey, function(message) {   //queued tested\n      setImmediate(() => {\n        winston.debug('SubscriptionNotifierQueued message.create');\n        subscriptionNotifier.subscribe('message.create', message);\n        winston.debug('SubscriptionNotifierQueued message.create sent');\n      });\n    });\n\n\n\n    var requestCreateKey = 'request.create';\n    if (requestEvent.queueEnabled) {\n      requestCreateKey = 'request.create.queue';\n    }\n    requestEvent.on(requestCreateKey, function(request) {  //queued tested\n      setImmediate(() => {\n        winston.debug('SubscriptionNotifier request.create');\n        subscriptionNotifier.subscribe('request.create', request);  \n        winston.debug('SubscriptionNotifier request.create sent');     \n      });\n    });\n\n\n\n    var requestUpdateKey = 'request.update';\n    if (requestEvent.queueEnabled) {\n      requestUpdateKey = 'request.update.queue';\n    }\n    requestEvent.on(requestUpdateKey, function(request) {    //queued tested\n      setImmediate(() => {\n        winston.debug('SubscriptionNotifier request.update');\n        subscriptionNotifier.subscribe('request.update', request);\n        winston.debug('SubscriptionNotifier request.update sent');     \n\n      });        \n    });\n\n\n    var requestCloseKey = 'request.close';   //request.close event here queued under job\n    if (requestEvent.queueEnabled) {\n      requestCloseKey = 'request.close.queue';\n    }\n    requestEvent.on(requestCloseKey, function(request) {   //request.close event here noqueued     //queued tested\n      winston.debug('SubscriptionNotifier request.close');\n      winston.debug(\"request.close event here 1\")\n      setImmediate(() => {\n        Message.find({recipient:  request.request_id, id_project: request.id_project}).sort({updatedAt: 'asc'}).exec(function(err, messages) {\n          var requestJson = request;\n          if (request.toJSON) {\n            requestJson = request.toJSON();\n          }\n          \n          requestJson.messages = messages;\n          subscriptionNotifier.subscribe('request.close', requestJson);\n          winston.debug('SubscriptionNotifier request.close sent');     \n\n        });   \n      });\n    });\n\n\n    var leadCreateKey = 'lead.create';   //lead.create event here queued under job\n    if (leadEvent.queueEnabled) {\n      leadCreateKey = 'lead.create.queue';\n    }\n    leadEvent.on(leadCreateKey, function(lead) {    //notqueued high\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('lead.create', lead);\n        winston.debug('SubscriptionNotifier lead.create sent');     \n      });\n    });\n\n    var authProjectUserUpdateKey = 'project_user.update';\n    if (authEvent.queueEnabled) {\n      authProjectUserUpdateKey = 'project_user.update.queue';\n    }\n    authEvent.on(authProjectUserUpdateKey,  function(event) {    //notqueued  high\n      setImmediate(() => {\n        subscriptionNotifier.subscribe('project_user.update', event.updatedProject_userPopulated);     \n        winston.debug('SubscriptionNotifier project_user.update sent');     \n      });\n    });\n\n\n    authEvent.on(\"project_user.update.agent\",  function(event) {  \n      setImmediate(() => {\n        subscriptionNotifier.subscribe('project_user.update.agent', event.updatedProject_userPopulated);     \n        winston.debug('SubscriptionNotifier project_user.update.agent sent');     \n      });\n    });\n    \n    winston.info('SubscriptionNotifierQueued started');\n  }\n\n\n\n\n};\n\nvar subscriptionNotifierQueued = new SubscriptionNotifierQueued();\n\n\nmodule.exports = subscriptionNotifierQueued;\n"
  },
  {
    "path": "services/testWsService.js",
    "content": "var Message = require(\"../models/message\");\nconst WebSocket = require('ws');\nvar url = require('url');\n\nclass TestWsService {\n\n \n\n  // https://hackernoon.com/nodejs-web-socket-example-tutorial-send-message-connect-express-set-up-easy-step-30347a2c5535\n  // https://medium.com/@martin.sikora/node-js-websocket-simple-chat-tutorial-2def3a841b61\n  init(server) {\n    \n    //var wss = new WebSocket.Server({ port: 40510 });\n    //var wss = new WebSocket.Server({ port: 40510 , path: \"/messages\" });\n    //var wss = new WebSocket.Server({  port: 80 ,path: \"/messages\" });\n     var wss = new WebSocket.Server({  server: server,path: \"/testws\" });\n    \n    var that = this;\n\n\n    // https://github.com/websockets/ws/blob/master/examples/express-session-parse/index.js\n\n\n    // IMPORTANTE https://blog.usejournal.com/using-mongodb-as-realtime-db-with-nodejs-c6f52c266750\n\n    // start mongo with: mongod --port 27017 --replSet rs0\n    wss.on('connection', function connection(ws, req) {\n\n      console.log('ws connection. req.url)', req.url);\n       //console.log('ws connection. req', req);\n       let urlParsed = url.parse(req.url, true);\n       console.log('urlParsed', urlParsed);\n       var queryParameter = urlParsed.query;\n       console.log('queryParameter', queryParameter);\n       console.log('queryParameter.q', queryParameter.q);\n       \n       var query = JSON.parse(queryParameter.q);    \n       console.log('query=', query);\n     \n      // Message.find({\"recipient\": requestid, id_project: id_project}).sort({updatedAt: 'asc'}).exec(function(err, messages) { \n        \n      // https://www.sitepoint.com/build-node-js-powered-chatroom-web-app-node-mongodb-socket/\n      // usa stream come qui \n\n        Message.find(query).sort({updatedAt: 'asc'}).limit(20).exec(function(err, messages) { \n        //Message.find({\"recipient\":\"support-group123\"}).sort({updatedAt: 'asc'}).limit(20).exec(function(err, messages) { \n          ws.send(JSON.stringify(messages));\n      });\n\n      \n      \n      setInterval(function() {\n        var message = {text:`ciao2 il ${new Date()}`};\n        // var array = new Array();\n        // array.push(message);\n        // ws.send(array);\n        ws.send(JSON.stringify(message));\n      },10000);\n\n    \n    \n\n\n\n      ws.on('message', function incoming(message) {\n        if (message.type === 'utf8') {\n          // process WebSocket message\n          \n        }\n        console.log('received: %s', message);\n       \n      });\n\n      // ws.on('open', function open() {\n      //   console.log('ws open');\n      // });\n\n    });\n\n \n\n  }\n\n\n}\n\nvar testWsService = new TestWsService();\nmodule.exports = testWsService;\n"
  },
  {
    "path": "services/trainingService.js",
    "content": "const faqBotEvent = require('../event/faqBotEvent');\nconst Faq_kb = require('../models/faq_kb');\nconst Faq = require('../models/faq');\nvar winston = require('../config/winston');\nconst axios = require(\"axios\").default;\nvar configGlobal = require('../config/global');\n\n\nlet training_api_url = process.env.CHATBOT_TRAINING_API_URL || \"http://34.65.210.38/model/enqueuetrain\";\nlet token = process.env.TRAINING_BOT_JWT_TOKEN;\n\nclass TrainingService {\n\n\n    async train(eventName, id_faq_kb, webhook_enabled) {\n\n        return new Promise((resolve, reject) => {\n            \n            Faq_kb.findById(id_faq_kb, (err, faq_kb) => {\n    \n                if (err) {\n                    winston.error(\"train error: \", err)\n                    // return null;\n                    reject(null);\n                }\n    \n                if (!faq_kb) {\n                    winston.error(\"faq_kb is undefined\");\n                    // return null;\n                    reject(null);\n                }\n    \n                if (faq_kb.intentsEngine !== 'tiledesk-ai') {\n                    winston.debug(\"intentsEngine: off\")\n                    // return null;\n                    reject(null);\n                }\n    \n                winston.debug(\"intentsEngine: on\")\n                Faq.find({ id_faq_kb: id_faq_kb }, async (err, faqs) => {\n    \n                    if (err) {\n                        winston.error(\"[Training] find all error: \", err);\n                    } else {\n    \n                        let json = {\n                            \"configuration\": {\n                                \"language\": faq_kb.language,\n                                \"pipeline\":[\"lstm\"]\n                            },\n                            \"model\": faq_kb._id,\n                            \"nlu\": [],\n                            //\"webhook_url\":  process.env.API_URL || configGlobal.apiUrl + \"/\" + faq_kb.id_project + \"/bots/\" + faq_kb._id+\"/training\",\n                            // curl -v -X PUT -H 'Content-Type:application/json' -u admin@tiledesk.com:adminadmin -d '{\"trained\":false}'  http://localhost:3000/63ed15febb8a5eb3b247fdfd/bots/64551b3422cdfb93ddb1b784\n                        }\n    \n                        if (webhook_enabled === true) {\n                            json.webhook_url = process.env.API_URL || configGlobal.apiUrl + \"/\" + faq_kb.id_project + \"/bots/\" + faq_kb._id+\"/training\"\n                        }\n\n                        let index = faqs.findIndex(f => f.intent_display_name === \"start\");\n                        faqs.slice(index);\n\n\n                        faqs.forEach((f) => {\n                            if (f.enabled == true) {\n                                let intent = {\n                                    \"intent\": f.intent_display_name,\n                                    \"examples\": []\n                                }\n                                if (f.question) {\n                                    let questions = f.question.split(\"\\n\");\n                                    intent.examples = questions;\n                                    json.nlu.push(intent);\n                                } else {\n                                    winston.debug(\"faq question null!\")\n                                }\n                            }\n                        })\n    \n                        winston.info(\"training json: \\n\", json);\n    \n                        await axios({\n                            url: training_api_url,\n                            headers: {\n                                'Content-Type': 'application/json',\n                                'Authorization': token\n                            },\n                            data: json,\n                            method: 'POST'\n                        }).then((resbody) => {\n                            winston.debug(\"[Training] resbody: \", resbody.data);\n                            // return true;\n                            resolve(resbody.data);\n                        }).catch((err) => {\n                            winston.error(\"[Training] error: \", err);\n                            // return false;\n                            reject(false);\n                            // winston.error(\"[Training] error: \", err);\n                        })\n                    }\n                })\n    \n            })\n\n        })\n\n\n\n    }\n\n    start() {\n        winston.info('TrainingService start');\n\n        faqBotEvent.on('faq_train.train', (id_faq_kb, webhook_enabled) => {\n            setImmediate(() => {\n                trainingService.train('faq_train.train', id_faq_kb, webhook_enabled);\n            })\n        })\n\n        faqBotEvent.on('faq_train.importedall', (id_faq_kb) => {\n            setImmediate(() => {\n                trainingService.train('faq.importedall', id_faq_kb);\n            })\n        })\n\n        faqBotEvent.on('faq_train.create', (id_faq_kb) => {\n            setImmediate(() => {\n                trainingService.train('faq_train.create', id_faq_kb);\n            })\n        })\n\n        faqBotEvent.on('faq_train.update', (id_faq_kb) => {\n            setImmediate(() => {\n                trainingService.train('faq_train.update', id_faq_kb);\n            })\n        })\n\n        faqBotEvent.on('faq_train.delete', (id_faq_kb) => {\n            setImmediate(() => {\n                trainingService.train('faq_train.delete', id_faq_kb);\n            })\n        })\n\n\n    }\n\n}\n\nvar trainingService = new TrainingService();\n\nmodule.exports = trainingService;"
  },
  {
    "path": "services/updateLeadQueued.js",
    "content": "'use strict';\n\nvar Request = require(\"../models/request\");\nvar messageService = require('../services/messageService');\nconst requestEvent = require('../event/requestEvent');\nconst leadEvent = require('../event/leadEvent');\nvar winston = require('../config/winston');\n\nclass UpdateLeadQueued {\n\n  constructor() {\n    // this.listen();\n  }\n\n  listen() {\n    // 12 marzo 2024 I disabled these two functions due to performance problems for a chatbot created by Sponziello \"Community bots Sendinblue Hubspot Qapla)\"\n    this.updateSnapshotLead();\n    this.sendMessageUpdateLead();\n    winston.info(\"Listening UpdateLeadQueued started\")\n\n  }\n\n\n  updateSnapshotLead() {\n\n    var leadUpdateKey = 'lead.update';\n    if (leadEvent.queueEnabled) {\n      leadUpdateKey = 'lead.update.queue';\n    }\n    winston.debug(\"leadUpdateKey: \" + leadUpdateKey);\n\n    leadEvent.on(leadUpdateKey, function (lead) {\n      setImmediate(() => {\n        winston.debug(\"updateSnapshotLead on lead.update \", lead);\n\n        var query = { lead: lead._id, id_project: lead.id_project };\n        winston.debug(\"query \", query);\n\n        Request.updateMany(query, { \"$set\": { \"snapshot.lead\": lead } }, function (err, updates) {\n          if (err) {\n            winston.error(\"Error updating requests updateSnapshotLead\", err);\n            return 0;\n          }\n          winston.debug(\"updateSnapshotLead updated for \" + updates.nModified + \" request\")\n          requestEvent.emit('request.update.snapshot.lead', { lead: lead, updates: updates });\n          return;\n        });\n        // Request.find({lead: lead._id, id_project: lead.id_project}, function(err, requests) {\n\n        //     if (err) {\n        //         winston.error(\"Error getting request by lead\", err);\n        //         return 0;\n        //     }\n        //     if (!requests || (requests && requests.length==0)) {\n        //         winston.warn(\"No request found for lead id \" +lead._id );\n        //         return 0;\n        //     }\n\n        //     requests.forEach(function(request) {\n\n\n        //     });\n\n        // });\n\n\n      });\n    });\n  }\n\n\n  sendMessageUpdateLead() {\n\n    var leadUpdateEmailKey = 'lead.fullname.email.update';\n    if (leadEvent.queueEnabled) {\n      leadUpdateEmailKey = 'lead.fullname.email.update.queue';\n    }\n    winston.debug(\"leadUpdateEmailKey: \" + leadUpdateEmailKey);\n\n\n    leadEvent.on(leadUpdateEmailKey, function (lead) {\n      winston.debug(\"lead.fullname.email.update \");\n      // leadEvent.on('lead.update', function(lead) {\n\n      setImmediate(() => {\n        winston.debug(\"sendMessageUpdateLead on lead.update \", lead);\n\n        // .find({\"channel.name\":\"chat21\"}).sort({\"createdAt\": -1}).limit(2)\n        Request.find({ lead: lead._id, id_project: lead.id_project, \"channel.name\":\"chat21\" }).limit(1).sort({\"createdAt\": -1}).\n        exec(function (err, requests) {\n\n          if (err) {\n            winston.error(\"Error getting sendMessageUpdateLead request by lead\", err);\n            return 0;\n          }\n          if (!requests || (requests && requests.length == 0)) {\n            winston.verbose(\"sendMessageUpdateLead No request found for lead id \" + lead._id);\n            return 0;\n          }\n\n          // winston.info(\"sendMessageUpdateLead requests \", requests);\n\n          // requests.forEach(function (request) {\n\n          var request = requests[0];\n\n          winston.debug(\"sendMessageUpdateLead request \", request);\n\n            // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language)\n          messageService.send(\n              'system',\n              'Bot',\n              // lead.fullname,                                \n              request.request_id,\n              \"Lead updated\",\n              request.id_project,\n              'system',\n              {\n                subtype: \"info/support\",\n                \"updateconversation\": false,\n                messagelabel: { key: \"LEAD_UPDATED\" },\n                updateUserEmail: lead.email,\n                updateUserFullname: lead.fullname\n              },\n              undefined,\n              request.language\n\n            );\n\n          // });\n\n        });\n\n      });\n    });\n  }\n\n\n\n\n\n}\n\n\nvar updateLeadQueued = new UpdateLeadQueued();\n\n\nmodule.exports = updateLeadQueued;\n\n"
  },
  {
    "path": "services/updateRequestSnapshotQueued.js",
    "content": "'use strict';\n\nvar Request = require(\"../models/request\");\nconst requestEvent = require('../event/requestEvent');\nvar winston = require('../config/winston');\n\nclass UpdateRequestSnapshotQueued {\n\n  constructor() {\n    // this.listen();\n  }\n\n  listen() {\n    this.updateRequestSnapshot();\n    winston.info(\"Listening UpdateRequestSnapshotQueued started\")\n  }\n\n  updateRequestSnapshot() {\n    let snapshotUpdateKey = 'request.snapshot.update';\n    if (requestEvent.queueEnabled) {\n      snapshotUpdateKey = 'request.snapshot.update.queue';\n    }\n    winston.debug(\"snapshotUpdateKey: \" + snapshotUpdateKey);\n\n    requestEvent.on(snapshotUpdateKey, function (data) {\n      setImmediate(() => {\n        winston.debug(\"updateRequestSnapshot on request.snapshot.update \", data);\n\n        const request = data.request;\n        const snapshot = data.snapshot;\n        const agentsArray = snapshot?.agents || [];\n\n        if (!request || !request.request_id || !request.id_project) {\n          winston.error(\"updateRequestSnapshot: Invalid request data\", data);\n          return;\n        }\n\n        // If there is no agents array in snapshot or it's not actually an array, skip the update\n        if (!Array.isArray(agentsArray)) {\n          winston.error(\"updateRequestSnapshot: snapshot.agents is not an array.\", snapshot);\n          return;\n        }\n\n        const query = { request_id: request.request_id, id_project: request.id_project };\n        winston.debug(\"updateRequestSnapshot query \", query);\n\n        // Update ONLY the snapshot.agents field, presupposing 'snapshot' document already exists\n        Request.findOneAndUpdate(\n          query, \n          { \"$set\": { \"snapshot.agents\": agentsArray } }, \n          { new: true },\n          function (err, updatedRequest) {\n            if (err) {\n              winston.error(\"Error updating request snapshot.agents in updateRequestSnapshot\", err);\n              return;\n            }\n            if (updatedRequest) {\n              winston.debug(\"updateRequestSnapshot updated snapshot.agents for request \" + updatedRequest.request_id);\n            } else {\n              winston.warn(\"updateRequestSnapshot: Request not found for \" + request.request_id);\n            }\n            return;\n          }\n        );\n      });\n    });\n  }\n}\n\nvar updateRequestSnapshotQueued = new UpdateRequestSnapshotQueued();\n\nmodule.exports = updateRequestSnapshotQueued;\n\n"
  },
  {
    "path": "services/userService.js",
    "content": "var User = require(\"../models/user\");\n// var Auth = require(\"../models/auth\");\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nclass UserService {\n\n    signup ( email, password, firstname, lastname, emailverified, phone) {\n        return new Promise(function (resolve, reject) {\n                \n            // winston.info(\"email: \" + email);\n            // var emailLowerCase = email;\n            \n            // if (email) {\n            var   emailLowerCase = email.toLowerCase();\n            // }\n\n            var newUser = new User({\n                _id: new mongoose.Types.ObjectId(),\n                email: emailLowerCase,\n                password: password,\n                firstname: firstname,\n                lastname: lastname,\n                emailverified: emailverified,  \n                phone: phone           \n            });\n            // save the user\n            newUser.save(function (err, savedUser) {\n                if (err) {\n                    if (err.code === 11000) { //error for dupes\n                        winston.warn('Error creating the user email already present');\n                        return reject(err);\n                    } else {\n                        winston.error('Error creating the user', err);\n                        return reject(err);\n                    }\n                    \n                }\n\n                winston.verbose('User created', savedUser.toObject());\n                return resolve(savedUser);\n            });\n        });\n    }\n\n}\n\nvar userService = new UserService();\n\nmodule.exports = userService;"
  },
  {
    "path": "services/webhookService.js",
    "content": "const faq_kb = require(\"../models/faq_kb\");\nconst httpUtil = require(\"../utils/httpUtil\");\nconst uuidv4 = require('uuid/v4');\nvar jwt = require('jsonwebtoken');\nvar winston = require('../config/winston');\nconst errorCodes = require(\"../errorCodes\");\nvar ObjectId = require('mongoose').Types.ObjectId;\n\nconst port = process.env.PORT || '3000';\nlet TILEBOT_ENDPOINT = \"http://localhost:\" + port + \"/modules/tilebot/\";;\nif (process.env.TILEBOT_ENDPOINT) {\n    TILEBOT_ENDPOINT = process.env.TILEBOT_ENDPOINT + \"/\"\n}\nwinston.debug(\"TILEBOT_ENDPOINT: \" + TILEBOT_ENDPOINT);\n\nclass WebhookService {\n\n    async run(webhook, payload, dev, redis_client) {\n\n        return new Promise(async (resolve, reject) => {\n\n            winston.verbose(\"(WebhookService) Run webhook \" + webhook.webhook_id);\n            let chatbot = await faq_kb.findById(webhook.chatbot_id).select(\"+secret\").catch((err) => {\n                winston.error(\"Error finding chatbot \", err);\n                reject(err);\n            })\n\n            if (!chatbot) {\n                winston.verbose(\"Chatbot not found with id \" + webhook.chatbot_id);\n                reject(\"Chatbot not found with id \" + webhook.chatbot_id);\n            }\n\n            let chatbot_id\n            if (chatbot.url) {\n                chatbot_id = chatbot.url.substr(chatbot.url.lastIndexOf(\"/\") + 1)\n            }\n            \n            if (dev) {\n                chatbot_id = webhook.chatbot_id;\n                let key = \"logs:webhook:\" + webhook.id_project + \":\" + webhook.webhook_id;\n                let value = await redis_client.get(key);\n                if (!value) {\n                    reject({ success: false, code: errorCodes.WEBHOOK.ERRORS.NO_PRELOADED_DEV_REQUEST})\n                    return;\n                }\n                let json_value = JSON.parse(value);\n                payload.preloaded_request_id = json_value.request_id;\n                payload.draft = true;\n            }  else {\n                payload.preloaded_request_id = \"automation-request-\" + webhook.id_project + \"-\" + new ObjectId() + \"-\" + webhook.webhook_id;\n            }\n\n            let token = await this.generateChatbotToken(chatbot);\n\n            let url = TILEBOT_ENDPOINT + 'block/' + webhook.id_project + \"/\" + chatbot_id + \"/\" + webhook.block_id;\n            winston.info(\"Webhook chatbot URL: \" + url);\n\n            payload.async = webhook.async;\n            payload.token = token;\n\n            if (process.env.NODE_ENV === 'test') {\n                resolve({ success: true, message: \"Webhook disabled in test mode\" });\n                return;\n            }\n\n            await httpUtil.post(url, payload).then((response) => {\n                resolve(response.data);\n            }).catch((err) => {\n                winston.error(\"Error calling webhook on post. Status \" + err?.status + \" StatusText \" + err?.statusText + \" Data \" + JSON.stringify(err?.response?.data));\n                reject(err);\n            })\n\n        })\n    }\n\n    async generateChatbotToken(chatbot) {\n        let signOptions = {\n          issuer: 'https://tiledesk.com',\n          subject: 'bot',\n          audience: 'https://tiledesk.com/bots/' + chatbot._id,\n          jwtid: uuidv4()\n        };\n      \n        let botPayload = chatbot.toObject();\n        let botSecret = botPayload.secret;\n      \n        var bot_token = jwt.sign(botPayload, botSecret, signOptions);\n        return bot_token;\n      }\n}\n\nlet webhookService = new WebhookService();\n\nmodule.exports = webhookService;"
  },
  {
    "path": "template/chatbot/blank.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n    \n    const custom_intent_id = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"reply\",\n                \"text\": \"I didn't understand. Can you rephrase your question?\",\n                \"attributes\": {\n                    \"commands\": [{\n                        \"type\": \"wait\",\n                        \"time\": 500\n                    }, {\n                        \"type\": \"message\",\n                        \"message\": {\n                            \"type\": \"text\",\n                            \"text\": \"I didn't understand. Can you rephrase your question?\"\n                        }\n                    }]\n                }\n            }],\n            \"intent_display_name\": \"defaultFallback\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 528\n                }\n            }\n        }, {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"intent\",\n                \"intentName\": \"#\" + custom_intent_id\n            }],\n            \"question\": \"\\\\start\",\n            \"intent_display_name\": \"start\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                }\n            }\n        }, {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"reply\",\n                \"attributes\": {\n                    \"disableInputMessage\": false,\n                    \"commands\": [{\n                        \"type\": \"wait\",\n                        \"time\": 500\n                    }, {\n                        \"type\": \"message\",\n                        \"message\": {\n                            \"type\": \"text\",\n                            \"text\": \"Hi, how can I help you?\"\n                        }\n                    }]\n                },\n                \"text\": \"Hi, how can I help you?\\r\\n\"\n            }],\n            \"intent_display_name\": \"welcome\",\n            \"intent_id\": custom_intent_id,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 113\n                }\n            }\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/blank_copilot.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n\n    const custom_intent_id = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"intent\",\n                \"intentName\": \"#\" + custom_intent_id\n            }],\n            \"question\": \"\",\n            \"intent_display_name\": \"webhook\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                }\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"web_response\",\n                \"status\": 200,\n                \"bodyType\": \"json\",\n                \"payload\": '{\"title\": \"Official Copilot\", \"text\": \"This is a suggestion!\"}'\n            }],\n            \"intent_display_name\": \"response\",\n            \"intent_id\": custom_intent_id,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 113\n                }\n            }\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/blank_voice.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n\n    const custom_intent_id = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"intent\",\n                \"intentName\": \"#\" + custom_intent_id\n            }],\n            \"question\": \"\\\\start\",\n            \"intent_display_name\": \"start\",\n            \"agents_available\": false,\n            \"attributes\": {\n                \"readonly\": true,\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                }\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"reply\",\n                \"text\": \"I didn't understand. Can you rephrase your question?\",\n                \"attributes\": {\n                    \"commands\": [{\n                        \"type\": \"wait\",\n                        \"time\": 500\n                    }, {\n                        \"type\": \"message\",\n                        \"message\": {\n                            \"type\": \"text\",\n                            \"text\": \"I didn't understand. Can you rephrase your question?\"\n                        }\n                    }]\n                },\n            }],\n            \"intent_display_name\": \"defaultFallback\",\n            \"attributes\": {\n                \"readonly\": true,\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 528\n                }\n            }\n        }, {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [\n                {\n                    \"_tdActionType\": \"play_prompt\",\n                    \"attributes\": {\n                        \"disableInputMessage\": false,\n                        \"commands\": [{\n                            \"type\": \"wait\",\n                            \"time\": 0\n                        }, {\n                            \"type\": \"message\",\n                            \"message\": {\n                                \"type\": \"text\",\n                                \"text\": \"Hi, how can I help you?\"\n                            }\n                        }, {\n                            \"type\": \"wait\",\n                            \"time\": 0\n                        }, {\n                            \"type\": \"settings\",\n                            \"settings\": {\n                                \"bargein\": true\n                            },\n                            \"subType\": \"play_prompt\"\n                        }]\n                    }\n                }\n            ],\n            \"intent_display_name\": \"welcome\",\n            \"intent_id\": custom_intent_id,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 113\n                },\n            }\n        }, {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"close\"\n            }],\n            \"intent_display_name\": \"close\",\n            \"attributes\": {\n                \"readonly\": true,\n                \"color\": \"204,68,75\",\n                \"position\": {\n                    \"x\": 399,\n                    \"y\": 531\n                }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "template/chatbot/blank_voice_twilio.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n\n    const custom_intent_id = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [\n                {\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + custom_intent_id\n                }\n            ],\n            \"question\": \"\\\\start\",\n            \"intent_display_name\": \"start\",\n            \"agents_available\": false,\n            \"attributes\": {\n                \"readonly\": true,\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                }\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"reply\",\n                \"text\": \"I didn't understand. Can you rephrase your question?\",\n                \"attributes\": {\n                    \"commands\": [{\n                        \"type\": \"wait\",\n                        \"time\": 500\n                    }, {\n                        \"type\": \"message\",\n                        \"message\": {\n                            \"type\": \"text\",\n                            \"text\": \"I didn't understand. Can you rephrase your question?\",\n                        }\n                    }]\n                }\n            }],\n            \"intent_display_name\": \"defaultFallback\",\n            \"agents_available\": false,\n            \"attributes\": {\n                \"readonly\": true,\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 528\n                }\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [\n                {\n                    \"_tdActionType\": \"play_prompt\",\n                    \"attributes\": {\n                        \"disableInputMessage\": false,\n                        \"commands\": [{\n                            \"type\": \"wait\",\n                            \"time\": 0\n                        }, {\n                            \"type\": \"message\",\n                            \"message\": {\n                                \"type\": \"text\",\n                                \"text\": \"Hi, how can I help you?\",\n                            }\n                        }, {\n                            \"type\": \"wait\",\n                            \"time\": 0\n                        }, {\n                            \"type\": \"settings\",\n                            \"settings\": {\n                                \"bargein\": true\n                            },\n                            \"subType\": \"play_prompt\"\n                        }]\n                    }\n                }\n            ],\n            \"intent_id\": custom_intent_id,\n            \"intent_display_name\": \"welcome\",\n            \"agents_available\": false,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 113\n                }\n            },\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"close\"\n            }],\n            \"intent_display_name\": \"close\",\n            \"agents_available\": false,\n            \"attributes\": {\n                \"readonly\": true,\n                \"color\": \"204,68,75\",\n                \"position\": {\n                    \"x\": 399,\n                    \"y\": 531\n                }\n            }\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/blank_webhook.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n\n    const custom_intent_id = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"intent\",\n                \"intentName\": \"#\" + custom_intent_id\n            }],\n            \"question\": \"\",\n            \"intent_display_name\": \"webhook\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                }\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"web_response\",\n                \"status\": 200,\n                \"bodyType\": \"json\",\n                \"payload\": '{\"success\": true , \"message\": \"Your webhook is online!\"}'\n            }],\n            \"intent_display_name\": \"response\",\n            \"intent_id\": custom_intent_id,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 714,\n                    \"y\": 113\n                }\n            }\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/empty.js",
    "content": "module.exports = function generateTemplate(options) {\n    return []\n}"
  },
  {
    "path": "template/chatbot/example.js",
    "content": "const ActionsConstants = require('../../models/actionsConstants');\n\nmodule.exports = function generateTemplate(options) {\n\n    return [\n        {\n            'question': 'Hi',\n            'answer': 'Hi',\n            'topic': 'greetings'\n        },\n        {\n            'question': 'Hello',\n            'answer': 'Hello',\n            'topic': 'greetings'\n        },\n        {\n            'question': 'Who are you?',\n            'answer': 'Hi, I\\'m a bot 🤖. You can find more about me [here](https://tiledesk.com/chatbot-for-customer-service).\\ntdImage:https://console.tiledesk.com/assets/images/tily-welcomebot.gif\\n* See the website https://tiledesk.com/\\n* Back to start tdAction:start',\n            'topic': 'greetings'\n        },\n        {\n            'question': '👨🏻‍🦰 I want an agent',\n            'answer': 'We are looking for an operator.. ' + ActionsConstants.CHAT_ACTION_MESSAGE.AGENT,\n            'intent_display_name': 'agent_handoff',\n            'topic': 'internal'\n        },\n        {\n            'question': 'Close\\nResolved',\n            'answer': ActionsConstants.CHAT_ACTION_MESSAGE.CLOSE,\n            'topic': 'internal'\n        },\n        {\n            'question': '\\\\start',\n            'answer': 'Hello 👋. I\\'m a bot 🤖.\\n\\nChoose one of the options below or write a message to reach our staff.\\n* Who are you?\\n* Where are you?\\n* What can you do?\\n* 👨🏻‍🦰 I want an agent',\n            'intent_display_name': 'start',\n            'topic': 'internal'\n        },\n        {\n            'question': 'defaultFallback',\n            'answer': 'I can not provide an adequate answer. Write a new question or talk to a human agent.\\n* Back to start tdAction:start\\n* See the docs https://docs.tiledesk.com/\\n* 👨🏻‍🦰 I want an agent',\n            'intent_display_name': 'defaultFallback',\n            'topic': 'internal'\n        },\n        {\n            'question': 'What can you do?',\n            'answer': 'Using natural language processing, I\\'m able to find the best answer for your users. I also support images, videos etc.. Let\\'s try:\\n* Sample Image\\n* Sample Video\\n* Sample Action tdAction:action1\\n* Sample Frame\\n* Back to start tdAction:start',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Sample Image',\n            'answer': 'tdImage:https://tiledesk.com/wp-content/uploads/2022/07/tiledesk_v2.png\\n* What can you do?\\n* Back to start tdAction:start',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Sample Frame',\n            'answer': 'tdFrame:https://www.emanueleferonato.com/wp-content/uploads/2019/02/runner/\\n* What can you do?\\n* Back to start tdAction:start',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Sample Video',\n            'answer': 'tdVideo:https://www.youtube.com/embed/EngW7tLk6R8\\n* What can you do?\\n* Back to start tdAction:start',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Where are you?',\n            'answer': 'We are here ❤️\\ntdFrame:https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d6087916.923447935!2d8.234804542117423!3d41.836572992140624!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x12d4fe82448dd203%3A0xe22cf55c24635e6f!2sItaly!5e0!3m2!1sen!2sit!4v1613657475377!5m2!1sen!2sit\\n* Back to start tdAction:start',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Sample Action',\n            'answer': 'Hello 👋 Would you like to take a closer look at our offer?\\n* Yes, please tdAction:yes_action\\n* No tdAction:no_action',\n            'intent_display_name': 'action1',\n            'topic': 'sample'\n        },\n        {\n            'question': 'Yes Action',\n            'answer': 'Great! Take a look here:\\n* Tiledesk Pricing https://tiledesk.com/pricing-cloud/',\n            'intent_display_name': 'yes_action',\n            'topic': 'sample'\n        },\n        {\n            'question': 'No Action',\n            'answer': 'All right! If you need anything, let us know.',\n            'intent_display_name': 'no_action',\n            'topic': 'sample'\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/handoff.js",
    "content": "const ActionsConstants = require('../../models/actionsConstants');\n\nmodule.exports = function generateTemplate(options) {\n\n    return [\n        {\n            'question': '\\\\start',\n            'answer': 'Hello',\n            'intent_display_name': 'start',\n            'topic': 'internal'\n        },\n        {\n            'question': '👨🏻‍🦰 I want an agent',\n            'answer': 'We are looking for an operator.. ' + ActionsConstants.CHAT_ACTION_MESSAGE.AGENT,\n            'intent_display_name': 'agent_handoff',\n            'topic': 'internal'\n        },\n        {\n            'question': 'defaultFallback',\n            'answer': 'I can not provide an adequate answer. Write a new question or talk to a human agent.\\n* Back to start tdAction:start\\n* See the docs https://docs.tiledesk.com/\\n* 👨🏻‍🦰 I want an agent', \n            'intent_display_name': 'defaultFallback', \n            'topic': 'internal'\n        }\n    ]\n}"
  },
  {
    "path": "template/chatbot/index.js",
    "content": "\nmodule.exports = {\n    blank: require('./blank'),\n    blank_webhook: require('./blank_webhook'),\n    blank_copilot: require('./blank_copilot'),\n    official_copilot: require('./official_copilot'),\n    blank_voice: require('./blank_voice'),\n    blank_voice_twilio: require('./blank_voice_twilio'),\n    empty: require('./empty'),\n    example: require('./example'),\n    handoff: require('./handoff')\n}"
  },
  {
    "path": "template/chatbot/official_copilot.js",
    "content": "const uuidv4 = require('uuid/v4');\n\nmodule.exports = function generateTemplate(options) {\n\n    let namespace_id = options.namespace_id;\n\n    const get_message_intent = uuidv4();\n    const get_request_intent = uuidv4();\n    const no_msg_or_req_intent = uuidv4();\n    const check_message_intent = uuidv4();\n    const check_no_issue_intent = uuidv4();\n    const check_question_intent = uuidv4();\n    const extract_issue_ai_intent = uuidv4();\n    const check_request_id_intent = uuidv4();\n    const extract_question_intent = uuidv4();\n    const create_transcript_intent = uuidv4();\n    const ask_kb_from_request_intent = uuidv4();\n    const ask_kb_from_message_intent = uuidv4();\n    const return_no_content_1_intent = uuidv4();\n    const return_no_content_2_intent = uuidv4();\n    const return_no_content_3_intent = uuidv4();\n    const return_no_content_4_intent = uuidv4();\n    const return_response_message_intent = uuidv4();\n    const return_response_request_intent = uuidv4();\n\n    return [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"intent\",\n                \"intentName\": \"#\" + get_message_intent\n            }],\n            \"intent_display_name\": \"webhook\",\n            \"question\": \"\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 172,\n                    \"y\": 384\n                },\n                \"readonly\": true,\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"jsoncondition\",\n                \"groups\": [\n                    {\n                        \"type\": \"expression\",\n                        \"conditions\": [\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"message\",\n                                \"operator\": \"isEmpty\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            },\n                            {\n                                \"type\": \"operator\",\n                                \"operator\": \"OR\"\n                            },\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"message\",\n                                \"operator\": \"isNull\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            },\n                            {\n                                \"type\": \"operator\",\n                                \"operator\": \"OR\"\n                            },\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"message\",\n                                \"operator\": \"isUndefined\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            }\n                        ]\n                    }\n                ],\n                \"stopOnConditionMet\": true,\n                \"noelse\": true,\n                \"trueIntent\": \"#\" + check_request_id_intent\n            }],\n            \"intent_display_name\": \"check_message\",\n            \"intent_id\": check_message_intent,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 960,\n                    \"y\": 245\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"e459225f-0a01-4786-b987-7fa3d4421877\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + extract_issue_ai_intent\n                },\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"askgptv2\",\n                \"question\": \"{{gpt_reply}}\",\n                \"assignReplyTo\": \"kb_reply\",\n                \"assignSourceTo\": \"kb_source\",\n                \"max_tokens\": 1024,\n                \"temperature\": 0.7,\n                \"top_k\": 10,\n                \"model\": \"gpt-4o\",\n                \"preview\": [],\n                \"history\": false,\n                \"citations\": true,\n                \"namespace\": namespace_id,\n                \"context\": \"\",\n                \"trueIntent\": \"#\" + return_response_message_intent,\n                \"falseIntent\": \"#\" + return_no_content_4_intent\n            }],\n            \"intent_display_name\": \"ask_kb_from_message\",\n            \"intent_id\": ask_kb_from_message_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2477,\n                    \"y\": 801\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"gpt_task\",\n                \"max_tokens\": 256,\n                \"temperature\": 0.7,\n                \"model\": \"gpt-4o\",\n                \"assignReplyTo\": \"gpt_reply\",\n                \"preview\": [],\n                \"formatType\": \"none\",\n                \"question\": \"This is a user message taken from support conversation.\\n\\n{{message}}\\n\\nCheck that the message contains a question or poses a problem. If there is, make it clear and concise as if the user were writing, otherwise reply with \\\"NULL\\\".\",\n                \"context\": \"\",\n                \"trueIntent\": \"#\" + check_no_issue_intent\n            }],\n            \"intent_display_name\": \"extract_issue_ai\",\n            \"intent_id\": extract_issue_ai_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 1510,\n                    \"y\": 473\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"jsoncondition\",\n                \"groups\": [\n                    {\n                        \"type\": \"expression\",\n                        \"conditions\": [\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"gpt_reply\",\n                                \"operator\": \"equalAsStrings\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"NULL\",\n                                    \"name\": \"\"\n                                }\n                            }\n                        ]\n                    }\n                ],\n                \"stopOnConditionMet\": true,\n                \"noelse\": true,\n                \"trueIntent\": \"#\" + return_no_content_3_intent\n            }],\n            \"intent_display_name\": \"check_no_issue\",\n            \"intent_id\": check_no_issue_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 1978,\n                    \"y\": 557\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"37185650-df70-4b90-9784-bcff646e37ce\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + ask_kb_from_message_intent\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{}\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"400\"\n            }],\n            \"intent_display_name\": \"return_no_content_3\",\n            \"intent_id\": return_no_content_3_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2465,\n                    \"y\": 461\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"jsoncondition\",\n                \"groups\": [\n                    {\n                        \"type\": \"expression\",\n                        \"conditions\": [\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"request_id\",\n                                \"operator\": \"isEmpty\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            },\n                            {\n                                \"type\": \"operator\",\n                                \"operator\": \"OR\"\n                            },\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"request_id\",\n                                \"operator\": \"isNull\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            },\n                            {\n                                \"type\": \"operator\",\n                                \"operator\": \"OR\"\n                            },\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"request_id\",\n                                \"operator\": \"isUndefined\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"\",\n                                    \"name\": \"\"\n                                }\n                            }\n                        ]\n                    }\n                ],\n                \"stopOnConditionMet\": true,\n                \"noelse\": true,\n                \"trueIntent\": \"#\" + no_msg_or_req_intent\n            }],\n            \"intent_display_name\": \"check_request_id\",\n            \"intent_id\": check_request_id_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 1505,\n                    \"y\": 52\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"a32cc3aa-8fd2-4ef4-8129-06ec8de6de32\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + get_request_intent\n                },\n                \"color\": \"80,100,147\",\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"setattribute-v2\",\n                \"operation\": {\n                    \"operands\": [\n                        {\n                            \"value\": \"{{result | json}}\",\n                            \"isVariable\": false\n                        }\n                    ],\n                    \"operators\": []\n                },\n                \"destination\": \"messages\"\n            },\n            {\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"code\",\n                \"source\": \"let messages = context.attributes[\\\"messages\\\"]\\nmessages = JSON.parse(messages);\\n\\nlet transcript = \\\"\\\";\\nmessages?.forEach(message => {\\n    // only unuseful messages have subtype\\n    const subtype = message?.attributes?.subtype;\\n\\n    // only chatbots\\n    const intentName = message?.attributes?.intentName;\\n\\n    // only end-users\\n    // const requester_id = message?.attributes?.requester_id;\\n\\n    // only service messages\\n    const messagelabel = message?.attributes?.messagelabel;\\n\\n    // human only messages\\n    if (!subtype && !messagelabel && !intentName) {\\n      //console.log(\\\"message:\\\", message);\\n      let text = message.senderFullname + \\\": \\\" + message.text;\\n      transcript += text + \\\"\\\\n\\\";\\n    }\\n  });\\n\\ncontext.setAttribute(\\\"transcript\\\", transcript);\"\n            }],\n            \"intent_display_name\": \"create_transcript\",\n            \"intent_id\": create_transcript_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2724,\n                    \"y\": -217\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"dbe26ec5-5c96-47b4-a885-294289a45f00\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + extract_question_intent\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"setattribute-v2\",\n                \"operation\": {\n                    \"operands\": [\n                        {\n                            \"value\": \"{{payload.text}}\",\n                            \"isVariable\": false\n                        }\n                    ],\n                    \"operators\": []\n                },\n                \"destination\": \"message\"\n            },\n            {\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"setattribute-v2\",\n                \"operation\": {\n                    \"operands\": [\n                        {\n                            \"value\": \"{{payload.request_id}}\",\n                            \"isVariable\": false\n                        }\n                    ],\n                    \"operators\": []\n                },\n                \"destination\": \"request_id\"\n            }],\n            \"intent_display_name\": \"get_message\",\n            \"intent_id\": get_message_intent,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 527,\n                    \"y\": 247\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"282fc8fd-c315-4c66-ae5e-82513595e718\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + check_message_intent\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"400\",\n                \"bodyType\": \"json\",\n                \"payload\": \"{}\"\n            }],\n            \"intent_display_name\": \"no_msg_or_req\",\n            \"intent_id\": no_msg_or_req_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2019,\n                    \"y\": -183\n                },\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{}\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"404\"\n            }],\n            \"intent_display_name\": \"return_no_content_1\",\n            \"intent_id\": return_no_content_1_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 4170,\n                    \"y\": -251\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"url\": \"https://api.tiledesk.com/v3/public/requests/{{request_id}}/messages\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"settings\": {\n                    \"timeout\": 20000\n                },\n                \"jsonBody\": null,\n                \"bodyType\": \"none\",\n                \"formData\": [],\n                \"assignStatusTo\": \"status\",\n                \"assignErrorTo\": \"error\",\n                \"method\": \"GET\",\n                \"_tdActionType\": \"webrequestv2\",\n                \"assignResultTo\": \"result\",\n                \"trueIntent\": \"#\" + create_transcript_intent\n            }],\n            \"intent_display_name\": \"get_request\",\n            \"intent_id\": get_request_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2110,\n                    \"y\": 88\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{}\",\n                \"headersString\": {\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\",\n                    \"Content-Type\": \"application/json\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"404\"\n            }],\n            \"intent_display_name\": \"return_no_content_4\",\n            \"intent_id\": return_no_content_4_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2971,\n                    \"y\": 1064\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"gpt_task\",\n                \"max_tokens\": 256,\n                \"temperature\": 0.7,\n                \"model\": \"gpt-4o\",\n                \"assignReplyTo\": \"gpt_reply\",\n                \"preview\": [],\n                \"formatType\": \"none\",\n                \"question\": \"This is a support conversation.\\n\\n{{transcript}}\\n\\nAnalyze the conversation, summarize the question that was asked or the problem that was posed. If there are multiple problems or questions, try to consider the last unsolved problem.\\nIf there is no question or unsolved problem, respond with \\\"NULL\\\".\",\n                \"context\": \"\",\n                \"trueIntent\": \"#\" + check_question_intent\n            }],\n            \"intent_display_name\": \"extract_question\",\n            \"intent_id\": extract_question_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 3194,\n                    \"y\": -223\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{}\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"404\"\n            }],\n            \"intent_display_name\": \"return_no_content_2\",\n            \"intent_id\": return_no_content_2_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 4699,\n                    \"y\": 272\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{\\n\\\"title\\\": \\\"Copilot Official\\\",\\n\\\"question\\\": {{gpt_reply | json}},\\n\\\"text\\\": {{kb_reply | json}}\\n}\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"200\"\n            }],\n            \"intent_display_name\": \"return_response_request\",\n            \"intent_id\": return_response_request_intent,\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 4690,\n                    \"y\": 16\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"askgptv2\",\n                \"question\": \"{{gpt_reply}}\",\n                \"assignReplyTo\": \"kb_reply\",\n                \"assignSourceTo\": \"kb_source\",\n                \"max_tokens\": 1024,\n                \"temperature\": 0.7,\n                \"top_k\": 10,\n                \"model\": \"gpt-4o\",\n                \"preview\": [],\n                \"history\": false,\n                \"citations\": true,\n                \"namespace\": namespace_id,\n                \"context\": \"\",\n                \"trueIntent\": \"#\" + return_response_request_intent,\n                \"falseIntent\": \"#\" + return_no_content_2_intent\n            }],\n            \"intent_display_name\": \"ask_kb_from_request\",\n            \"intent_id\": ask_kb_from_request_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 4179,\n                    \"y\": 22\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"_tdActionType\": \"jsoncondition\",\n                \"groups\": [\n                    {\n                        \"type\": \"expression\",\n                        \"conditions\": [\n                            {\n                                \"type\": \"condition\",\n                                \"operand1\": \"gpt_reply\",\n                                \"operator\": \"equalAsStrings\",\n                                \"operand2\": {\n                                    \"type\": \"const\",\n                                    \"value\": \"NIENTE\",\n                                    \"name\": \"\"\n                                }\n                            }\n                        ]\n                    }\n                ],\n                \"stopOnConditionMet\": true,\n                \"noelse\": true,\n                \"trueIntent\": \"#\" + return_no_content_1_intent\n            }],\n            \"intent_display_name\": \"check_question\",\n            \"intent_id\": check_question_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 3653,\n                    \"y\": -127\n                },\n                \"nextBlockAction\": {\n                    \"_tdActionId\": \"11879363-4439-4723-82bc-efeb1e7c62ef\",\n                    \"_tdActionType\": \"intent\",\n                    \"intentName\": \"#\" + ask_kb_from_request_intent\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"actions\": [{\n                \"_tdActionTitle\": \"\",\n                \"payload\": \"{\\n\\\"title\\\": \\\"Copilot Official\\\",\\n\\\"question\\\": {{gpt_reply | json}},\\n\\\"text\\\": {{kb_reply | json}}\\n}\",\n                \"headersString\": {\n                    \"Content-Type\": \"*/*\",\n                    \"Cache-Control\": \"no-cache\",\n                    \"User-Agent\": \"TiledeskBotRuntime\",\n                    \"Accept\": \"*/*\"\n                },\n                \"bodyType\": \"json\",\n                \"assignTo\": \"\",\n                \"_tdActionType\": \"web_response\",\n                \"status\": \"200\"\n            }],\n            \"intent_display_name\": \"return_response_message\",\n            \"intent_id\": return_response_message_intent,\n            \"language\": \"en\",\n            \"attributes\": {\n                \"position\": {\n                    \"x\": 2969,\n                    \"y\": 841\n                },\n                \"readonly\": false\n            },\n            \"agents_available\": false\n        }\n    ]\n}\n\n\n"
  },
  {
    "path": "template/email/assignedEmailMessage.html",
    "content": "\n<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New message ticket from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <!--\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n      <div style=\"text-align:center\">\n        <a href=\"http://www.tiledesk.com\"\n          style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n          <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n        </a>\n      </div>\n    </tr>\n    -->\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>New Message from a ticket assigned to you</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      {{#ifEquals message.type \"text\"}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"image\"}}\n                      <img src=\"{{message.metadata.src}}\" />\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"file\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{/ifEquals}}\n\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Project name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Department name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.department.name}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      From email : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.email}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Contact name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.fullname}}</strong>\n                    </td>\n                  </tr>\n\n                  <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"content-block\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n                Assignee : <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\"></strong> \n                </td>\n                </tr> -->\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Channel : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                        {{#ifEquals request.channel.name \"chat21\"}}\n                        Chat\n                        {{else}}\n                        {{request.channel.name}}\n                        {{/ifEquals}}\n                      </strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <a\n                        href=\"{{baseScope.baseUrl}}/#/project/{{request.id_project}}/wsrequest/{{request.request_id}}/messages\">Open\n                        the dashboard</a>.\n\n                    </td>\n                  </tr>\n\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n          </table>\n\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n\n\n\n          </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n  </div>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/assignedRequest.html",
    "content": "<!DOCTYPE html\n    PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n    style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width\" />\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n    <title>New chat from Tiledesk</title>\n\n    <style type=\"text/css\">\n        img {\n            max-width: 100%;\n            margin-left: 16px;\n            text-align: center !important;\n        }\n\n        img.CToWUd {\n            margin-bottom: 16px;\n            max-width: 200px !important;\n            width: 200px !important;\n            min-width: 200px !important;\n            outline: none;\n            text-decoration: none;\n            border: none;\n            height: auto;\n            margin-left: 0px;\n        }\n\n        body {\n            -webkit-font-smoothing: antialiased;\n            -webkit-text-size-adjust: none;\n            width: 100% !important;\n            height: 100%;\n            line-height: 1.6em;\n        }\n\n        body {\n            background-color: #f6f6f6;\n        }\n\n        @media only screen and (max-width: 640px) {\n            body {\n                padding: 0 !important;\n            }\n\n            h1 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n                text-align: center !important;\n            }\n\n            h2 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h3 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h4 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h1 {\n                font-size: 22px !important;\n            }\n\n            h2 {\n                font-size: 18px !important;\n            }\n\n            h3 {\n                font-size: 16px !important;\n            }\n\n            .container {\n                padding: 0 !important;\n                width: 100% !important;\n            }\n\n            .content {\n                padding: 0 !important;\n            }\n\n            .content-wrap {\n                padding: 10px !important;\n            }\n\n            .invoice {\n                width: 100% !important;\n            }\n        }\n    </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n\n    {{#if baseScope.replyEnabled}}\n    <div>\\# Please type your reply above this line \\#</div>\n    {{/if}}\n\n\n    <table class=\"body-wrap\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n        bgcolor=\"#f6f6f6\">\n        <tr\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n            <td class=\"container\" width=\"600\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n                valign=\"top\">\n                <div class=\"content\"\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n                    <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n                        bgcolor=\"#fff\">\n\n                        <!--\n                <tr\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                  <div style=\"text-align:center\">\n                    <a href=\"http://www.tiledesk.com\"\n                      style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                      <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                    </a>\n                  </div>\n                </tr>\n                -->\n\n                        <tr\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                            <td class=\"alert alert-warning\"\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                                align=\"center\" valign=\"top\">\n                                <div>\n                                    <h2>New Chat assigned to you</h2>\n                                </div>\n\n                            </td>\n                        </tr>\n\n                        <tr\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <td class=\"content-wrap\"\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                                valign=\"top\">\n                                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            {{{msgText}}}\n                                        </td>\n                                    </tr>\n\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            Project name : <strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                                        </td>\n                                    </tr>\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            Department name : <strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.department.name}}</strong>\n                                        </td>\n                                    </tr>\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            Source page : <strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.sourcePage}}</strong>\n                                        </td>\n                                    </tr>\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            Contact name : <strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.fullname}}</strong>\n                                        </td>\n                                    </tr>\n\n                                    <!-- TODO DO IT -->\n                                    <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n                    Assignee : <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\"></strong> \n                    </td>\n                    </tr> -->\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            Channel : <strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                                                {{#ifEquals request.channel.name \"chat21\"}}\n                                                Chat\n                                                {{else}}\n                                                {{request.channel.name}}\n                                                {{/ifEquals}}\n\n\n                                            </strong>\n                                        </td>\n                                    </tr>\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            <a\n                                                href=\"{{baseScope.baseUrl}}/#/project/{{request.id_project}}/wsrequest/{{request.request_id}}/messages\">Open\n                                                the dashboard</a>.\n                                        </td>\n                                    </tr>\n\n\n\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                        </td>\n                                    </tr>\n                                </table>\n                            </td>\n                        </tr>\n\n                        <tr>\n                            <td>\n                                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                                    <span>Powered by </span>\n                                    <span style=\"display: flex;\"><img\n                                            src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\"\n                                            width=\"15\" height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                                    <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                                </div>\n\n                            </td>\n                        </tr>\n\n                    </table>\n                    <div class=\"footer\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n                        <table width=\"100%\"\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <tr\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                <td class=\"aligncenter content-block\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                                    align=\"center\" valign=\"top\">\n                                    <span><a href=\"http://www.tiledesk.com\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                                            Tiledesk.com </a></span>\n                                    <br><span><a href=\"%unsubscribe_url%\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                                </td>\n                            </tr>\n                        </table>\n\n                    </div>\n            </td>\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n        </tr>\n    </table>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/beenInvitedExistingUser.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New email from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n          \n          <div style=\"text-align:center\">\n            <!--\n            <a href=\"http://www.tiledesk.com\"\n              style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n              <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n            </a>\n            -->\n          </div>\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                  \n              </tr> -->\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <h2 style=\"text-align: center; letter-spacing: 1px; font-weight: 400; line-height:24px \">\n                        {{currentUserFirstname}} {{currentUserLastname}} has invited you to the Tiledesk project\n                        <strong> {{projectName}}</strong>\n                      </h2>\n\n                      <br> <br><strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">Hi\n                        {{invitedUserFirstname}} {{invitedUserLastname}},</strong>\n\n                      <br> <br>\n                      I invited you to take on the role of {{invitedUserRole}} of the Tiledesk <strong>\n                        {{projectName}}</strong> project\n\n\n                      <div style=\"text-align: center;\">\n                        <br><br>\n                        <a href=\"{{baseScope.baseUrl}}/#/project/{{id_project}}/home\"\n                          style=\" background-color: #ff8574 !important; border: none; color: white; padding: 12px 30px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer;\">\n                          GO TO THE PROJECT\n                        </a>\n                      </div>\n\n                      <br><br> The Tiledesk Team\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/beenInvitedNewUser.html",
    "content": "<!DOCTYPE html\n    PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n    style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width\" />\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n    <title>New email from Tiledesk</title>\n\n    <style type=\"text/css\">\n        img {\n            max-width: 100%;\n            margin-left: 16px;\n            text-align: center !important;\n        }\n\n        img.CToWUd {\n            margin-bottom: 16px;\n            max-width: 200px !important;\n            width: 200px !important;\n            min-width: 200px !important;\n            outline: none;\n            text-decoration: none;\n            border: none;\n            height: auto;\n            margin-left: 0px;\n        }\n\n        body {\n            -webkit-font-smoothing: antialiased;\n            -webkit-text-size-adjust: none;\n            width: 100% !important;\n            height: 100%;\n            line-height: 1.6em;\n        }\n\n        body {\n            background-color: #f6f6f6;\n        }\n\n        @media only screen and (max-width: 640px) {\n            body {\n                padding: 0 !important;\n            }\n\n            h1 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n                text-align: center !important;\n            }\n\n            h2 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h3 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h4 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h1 {\n                font-size: 22px !important;\n            }\n\n            h2 {\n                font-size: 18px !important;\n            }\n\n            h3 {\n                font-size: 16px !important;\n            }\n\n            .container {\n                padding: 0 !important;\n                width: 100% !important;\n            }\n\n            .content {\n                padding: 0 !important;\n            }\n\n            .content-wrap {\n                padding: 10px !important;\n            }\n\n            .invoice {\n                width: 100% !important;\n            }\n        }\n    </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n\n    <table class=\"body-wrap\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n        bgcolor=\"#f6f6f6\">\n        <tr\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n            <td class=\"container\" width=\"600\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n                valign=\"top\">\n                <div class=\"content\"\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n                    <div style=\"text-align:center\">\n                        <!--\n            <a href=\"http://www.tiledesk.com\"\n              style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n              <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n            </a>\n            -->\n                    </div>\n                    <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n                        bgcolor=\"#fff\">\n\n                        <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                \n            </tr> -->\n\n                        <tr\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <td class=\"content-wrap\"\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                                valign=\"top\">\n                                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                            <h2\n                                                style=\"text-align: center; letter-spacing: 1px; font-weight: 400; line-height:24px \">\n                                                {{currentUserFirstname}} {{currentUserLastname}} has invited you to the\n                                                Tiledesk project\n                                                <strong> {{projectName}}</strong>\n                                            </h2>\n\n                                            <!-- <br> <br><strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{to}},</strong> -->\n\n                                            <br> <br>\n                                            I invited you to take on the role of {{invitedUserRole}} of the Tiledesk\n                                            <strong>\n                                                {{projectName}}</strong> project\n\n\n                                            <div style=\"text-align: center;\">\n                                                <br><br>\n\n                                                <a href=\"{{baseScope.baseUrl}}/#/handle-invitation/{{pendinginvitationid}}/{{projectName}}/{{currentUserFirstname}}/{{currentUserLastname}}\"\n                                                    style=\" background-color: #ff8574 !important; border: none; color: white; padding: 12px 30px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer;\">\n                                                    GO TO THE PROJECT\n                                                </a>\n                                            </div>\n\n                                            <br><br> The Tiledesk Team\n                                        </td>\n                                    </tr>\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                                            valign=\"top\">\n                                        </td>\n                                    </tr>\n                                </table>\n                            </td>\n                        </tr>\n\n                        <tr>\n                            <td>\n                                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                                    <span>Powered by </span>\n                                    <span style=\"display: flex;\"><img\n                                            src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\"\n                                            width=\"15\" height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                                    <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                                </div>\n\n                            </td>\n                        </tr>\n\n                    </table>\n                    <div class=\"footer\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n                        <table width=\"100%\"\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <tr\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                <td class=\"aligncenter content-block\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                                    align=\"center\" valign=\"top\">\n                                    <span><a href=\"http://www.tiledesk.com\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                                            Tiledesk.com </a></span>\n                                    <br><span><a href=\"%unsubscribe_url%\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                                </td>\n                            </tr>\n                        </table>\n                    </div>\n                </div>\n            </td>\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n        </tr>\n    </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/checkpointReachedEmail.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New message from Tiledesk</title>\n\n  <style type=\"text/css\">\n    /* Aggiungi qui gli stili CSS */\n  </style>\n</head>\n\n<body style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0; background-color: #e2e7e9; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; padding: 30px 0px;\">\n\n  <table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n    <tr>\n      <td align=\"center\" valign=\"top\">\n        <table cellpadding=\"0\" cellspacing=\"0\" width=\"600\" style=\"background-color: #fff; border-radius: 8px;\">\n          <tr>\n            <td style=\"padding: 0px 30px;\">\n              <table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\">\n                <tr>\n                  <td style=\"text-align: center;\">\n                    <img src=\"https://img.freepik.com/vettori-premium/illustrazione-di-progettazione-del-pacchetto-piatto-di-finanza_205077-4142.jpg?w=2000\" alt=\"Header Image\" style=\"width: 100%; max-height: 250px; border-radius: 8px;\" />\n                  </td>\n                </tr>\n                <tr>\n                  <td style=\"text-align: center;\">\n                    <h1 style=\"color: #333;\">Update on monthly resources usage</h1>\n                  </td>\n                </tr>\n                <tr>\n                  <td style=\"padding: 10px 30px;\">\n                    <p>Dear {{firstname}},</p>\n                    <p>This is to inform you that you reached the <b>{{checkpoint}}%</b> of your monthly quote for <b>{{resource_name}}</b> on your project <b>{{project_name}}</b>.</p>\n                    <p>Below is a summary of the monthly resource usage:</p>\n                    <table cellpadding=\"0\" cellspacing=\"0\" width=\"100%\" style=\"border-collapse: collapse; margin-bottom: 20px; text-align: center;\">\n                      <tr>\n                        <th style=\"border: 1px solid #ddd; padding: 8px;\">Resource</th>\n                        <th style=\"border: 1px solid #ddd; padding: 8px;\">Quota</th>\n                        <th style=\"border: 1px solid #ddd; padding: 8px;\">Usage</th>\n                      </tr>\n                      <tr>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">Conversations</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{requests_quote}}</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{requests_perc}}%</td>\n                      </tr>\n                      <tr>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">AI Tokens</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{tokens_quote}}</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{tokens_perc}}%</td>\n                      </tr>\n                      <tr>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">Chatbot Email</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{email_quote}}</td>\n                        <td style=\"border: 1px solid #ddd; padding: 8px;\">{{email_perc}}%</td>\n                      </tr>\n                    </table>\n                    <p style=\"text-align: center; font-size: 14px; color: #777;\">This is an automated message. Please do not reply.</p>\n                    <div style=\"text-align: center; margin-top: 20px; padding: 20px;\">\n                      <img src=\"https://tiledesk.com/wp-content/uploads/2023/10/tiledesk-logo.png\" alt=\"Company Footer Logo\" style=\"max-width: 160px;\" />\n                    </div>\n                  </td>\n                </tr>\n              </table>\n            </td>\n          </tr>\n        </table>\n      </td>\n    </tr>\n    <tr>\n      <td align=\"center\" valign=\"top\" style=\"padding-top: 20px;\">\n        <table cellpadding=\"0\" cellspacing=\"0\" width=\"600\">\n          <tr>\n            <td style=\"text-align: center; padding: 6px;\">\n              <a href=\"http://www.tiledesk.com\" style=\"font-size: 16px; font-weight: 500; color: #3c3c3c; text-decoration: none; margin: 0; margin-bottom: 10px;\">Tiledesk.com</a><br />\n            </td>\n          </tr>\n          <tr>\n            <td style=\"text-align: center; padding: 6px;\">\n              <a href=\"%unsubscribe_url%\" style=\"font-size: 16px; font-weight: 500; color: #3c3c3c; text-decoration: none; margin: 0; margin-bottom: 10px;\">Unsubscribe</a>\n            </td>\n          </tr>\n        </table>\n      </td>\n    </tr>\n  </table>\n</body>\n\n</html>\n"
  },
  {
    "path": "template/email/emailDirect.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New Ticket from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  {{#if request_id}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n\n                <div style=\"text-align:center\">\n                  <!--\n                  <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                    <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                  </a>\n                  -->\n                </div>\n              </td>\n            </tr>\n\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <!-- <br><span><a href=\"%unsubscribe_url%\"  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>  -->\n                </td>\n              </tr>\n            </table>\n          </div>\n\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/newMessage.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New message from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <div style=\"text-align:center\">\n                <!--\n                <a href=\"http://www.tiledesk.com\"\n                  style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                  <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                </a>\n                -->\n              </div>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>New Message</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      {{#ifEquals message.type \"text\"}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"image\"}}\n                      <img src=\"{{message.metadata.src}}\" />\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"file\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{/ifEquals}}\n\n\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Project name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Agent name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.senderFullname}}</strong>\n                    </td>\n                  </tr>\n\n\n                  <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"content-block\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n                  Source page : <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.request.sourcePage}}</strong>\n                </td>\n                </tr> -->\n\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Click <a href=\"{{seamlessPage}}{{tokenQueryString}}\">here</a> to continue the conversation by\n                      chat.\n                    </td>\n                  </tr>\n\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n\n\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/newMessageFollower.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New Ticket from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n\n                <div style=\"text-align:center\">\n                  <!--\n                    <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                      <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                    </a>\n                    -->\n                </div>\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>You are a follower of the ticket number: #{{message.request.ticket_id}}</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Sender: <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.senderFullname}}</strong>\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      {{#ifEquals message.type \"text\"}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"image\"}}\n                      <img src=\"{{message.metadata.src}}\" />\n                      {{#if msgText}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/if}}\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"file\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"frame\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{#if msgText}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/if}}\n                      {{/ifEquals}}\n\n                      {{#if message.attributes}}\n                      {{#if message.attributes.attachment}}\n                      {{#if message.attributes.attachment.buttons}}\n                      {{#each message.attributes.attachment.buttons}}\n                      {{#ifEquals this.type \"url\"}}\n                      <li><a href=\"{{this.link}}\" class=\"dynamic_button\">{{this.value}}</a></li>\n                      {{else}}\n                      <li><a\n                          href=\"mailto:{{../message.request.request_id}}@{{../baseScope.inboundDomain}}?subject=Re:%20{{../message.request.subject}}&body={{this.value}}\"\n                          class=\"dynamic_button\">{{this.value}}</a></li>\n                      {{/ifEquals}}\n                      {{/each}}\n\n\n                      {{/if}}\n                      {{/if}}\n                      {{/if}}\n\n                      <!-- {{#if message.attributes.intent_info}}\n    {{#if message.attributes.intent_info.others}}\n      Others:\n      {{#each message.attributes.intent_info.others}}\n        <li><span>{{this.answer}}</span></li>\n      {{/each}}\n    {{/if}}\n  {{/if}} -->\n\n\n\n\n\n                    </td>\n                  </tr>\n\n\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Project name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Department name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.request.department.name}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Source page : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.request.sourcePage}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Contact name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.request.lead.fullname}}</strong>\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Channel : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                        {{#ifEquals message.request.channel.name \"chat21\"}}\n                        Chat\n                        {{else}}\n                        {{message.request.channel.name}}\n                        {{/ifEquals}}\n\n\n                      </strong>\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Priority : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.request.priority}}</strong>\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <!-- <br><span><a href=\"%unsubscribe_url%\"  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>  -->\n                </td>\n              </tr>\n            </table>\n          </div>\n\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/passwordChanged.html",
    "content": "<!DOCTYPE html\n    PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n    style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n    <meta name=\"viewport\" content=\"width=device-width\" />\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n    <title>New email from Tiledesk</title>\n\n    <style type=\"text/css\">\n        img {\n            max-width: 100%;\n            margin-left: 16px;\n            text-align: center !important;\n        }\n\n        img.CToWUd {\n            margin-bottom: 16px;\n            max-width: 200px !important;\n            width: 200px !important;\n            min-width: 200px !important;\n            outline: none;\n            text-decoration: none;\n            border: none;\n            height: auto;\n            margin-left: 0px;\n        }\n\n        body {\n            -webkit-font-smoothing: antialiased;\n            -webkit-text-size-adjust: none;\n            width: 100% !important;\n            height: 100%;\n            line-height: 1.6em;\n        }\n\n        body {\n            background-color: #f6f6f6;\n        }\n\n        @media only screen and (max-width: 640px) {\n            body {\n                padding: 0 !important;\n            }\n\n            h1 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n                text-align: center !important;\n            }\n\n            h2 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h3 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h4 {\n                font-weight: 800 !important;\n                margin: 20px 0 5px !important;\n            }\n\n            h1 {\n                font-size: 22px !important;\n            }\n\n            h2 {\n                font-size: 18px !important;\n            }\n\n            h3 {\n                font-size: 16px !important;\n            }\n\n            .container {\n                padding: 0 !important;\n                width: 100% !important;\n            }\n\n            .content {\n                padding: 0 !important;\n            }\n\n            .content-wrap {\n                padding: 10px !important;\n            }\n\n            .invoice {\n                width: 100% !important;\n            }\n        }\n    </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n\n    <table class=\"body-wrap\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n        bgcolor=\"#f6f6f6\">\n        <tr\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n            <td class=\"container\" width=\"600\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n                valign=\"top\">\n                <div class=\"content\"\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n                    <div style=\"text-align:center\">\n                        <!--\n                <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                    <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                </a>\n                  -->\n                    </div>\n                    <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n                        bgcolor=\"#fff\">\n\n                        <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    \n                </tr> -->\n\n                        <tr\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <td class=\"content-wrap\"\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                                valign=\"top\">\n                                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                                            valign=\"top\">\n                                            <h2 style=\"text-align: center; letter-spacing: 1px; \">\n                                                Your password has been changed\n                                            </h2>\n\n                                            <br>\n                                            <br<strong\n                                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                                Hi {{userFirstname}} {{userLastname}},</strong>\n\n                                                <br> <br>\n                                                The password of your Tiledesk account {{to}} was just changed.\n                                                <br><br>If this was you, then you can safely ignore this email.\n                                                <br><br>If this wasn't you please contact <a\n                                                    href=\"mailto:info@tiledesk.com\">our support\n                                                    team</a>\n\n                                                <br><br> The Tiledesk Team\n                                        </td>\n                                    </tr>\n\n                                    <tr\n                                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                        <td class=\"content-block\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                                            valign=\"top\">\n                                        </td>\n                                    </tr>\n                                </table>\n                            </td>\n                        </tr>\n\n                        <tr>\n                            <td>\n                                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                                    <span>Powered by </span>\n                                    <span style=\"display: flex;\"><img\n                                            src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\"\n                                            width=\"15\" height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                                    <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                                </div>\n\n                            </td>\n                        </tr>\n\n\n\n                    </table>\n                    <div class=\"footer\"\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n                        <table width=\"100%\"\n                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                            <tr\n                                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                                <td class=\"aligncenter content-block\"\n                                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;\"\n                                    align=\"center\" valign=\"top\">\n                                    <span><a href=\"http://www.tiledesk.com\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                                            Tiledesk.com </a></span>\n                                    <br><span><a href=\"%unsubscribe_url%\"\n                                            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                                </td>\n                            </tr>\n                        </table>\n                    </div>\n                </div>\n            </td>\n            <td style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                valign=\"top\"></td>\n        </tr>\n    </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/pooledEmailMessage.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New message ticket from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <div style=\"text-align:center\">\n                <!--\n            <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n            </a>\n              -->\n              </div>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>New Message from a pooled ticket</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      {{#ifEquals message.type \"text\"}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"image\"}}\n                      <img src=\"{{message.metadata.src}}\" />\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"file\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{/ifEquals}}\n\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Project name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Department name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.department.name}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      From email : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.email}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Contact name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.fullname}}</strong>\n                    </td>\n                  </tr>\n\n                  <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"content-block\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\" valign=\"top\">\n                Assignee : <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\"></strong> \n                </td>\n                </tr> -->\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Channel : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                        {{#ifEquals request.channel.name \"chat21\"}}\n                        Chat\n                        {{else}}\n                        {{request.channel.name}}\n                        {{/ifEquals}}\n\n\n                      </strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <a\n                        href=\"{{baseScope.baseUrl}}/#/project/{{request.id_project}}/wsrequest/{{request.request_id}}/messages\">Open\n                        the dashboard</a>.\n\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n\n                  <tr>\n                    <td>\n                      <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                      <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                        <span>Powered by </span>\n                        <span style=\"display: flex;\"><img\n                            src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\"\n                            width=\"15\" height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                        <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                      </div>\n\n                    </td>\n                  </tr>\n\n                </table>\n              </td>\n            </tr>\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n\n\n\n          </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/pooledRequest.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New pooled chat from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <div style=\"text-align:center\">\n                <!--\n                <a href=\"http://www.tiledesk.com\"\n                  style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                  <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n                </a>\n                -->\n              </div>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>New Pooled Chat</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Project name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{project.name}}</strong>\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Department name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.department.name}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Source page : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.sourcePage}}</strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Contact name : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.lead.fullname}}</strong>\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Channel : <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                        {{#ifEquals request.channel.name \"chat21\"}}\n                        Chat\n                        {{else}}\n                        {{request.channel.name}}\n                        {{/ifEquals}}\n\n\n                      </strong>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <a\n                        href=\"{{baseScope.baseUrl}}/#/project/{{request.id_project}}/wsrequest/{{request.request_id}}/messages\">Open\n                        the dashboard</a>.\n\n                    </td>\n                  </tr>\n\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n\n\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/redirectToDesktopEmail.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>Join Tiledesk from Desktop</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    .header-image-container {\n      margin-top: 10px;\n      height: 40px;\n      background-image: url(\"https://tiledesk.com/wp-content/uploads/2022/08/tiledesk_v1-1.png\");\n      background-size: contain;\n      background-position: center;\n      background-repeat: no-repeat;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <div class=\"header-image-container\"></div>\n                  <h2 style=\"margin-top: 40px;\">Get Ready for a Full Tiledesk Experience!</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\">\n                      <div style=\"text-align: center;\">\n                        For the optimal Tiledesk experience, we recommend using it on a desktop device. We've made\n                        access easier for you with a personalized link.\n\n                        <div style=\"margin-top: 20px; font-weight: 600;\">Access Tiledesk on your desktop now 👇</div>\n\n                        <div style=\"margin-top: 10px;\">\n                          <!-- // Change it with config parameter -->\n                          <!-- <a href=\"{{baseScope.baseUrl}}/#/project/{{project_id}}/home?token={{token}}\"\n                            style=\" background-color: #ff8574 !important; border: none; color: white; padding: 6px 18px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer; border-radius: 8px;\">\n                            Enjoy Tiledesk\n                          </a> -->\n                          <a href=\"{{redirect_url}}\"\n                            style=\" background-color: #ff8574 !important; border: none; color: white; padding: 6px 18px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer; border-radius: 8px;\">\n                            Enjoy Tiledesk\n                          </a>\n\n                        </div>\n\n                      </div>\n\n                      <div style=\"margin-top: 40px;\">\n                        Alternatively, simply copy and paste this URL into your browser:\n                        <div>\n                          <!-- <a\n                          href=\"{{baseScope.baseUrl}}/#/project/{{project_id}}/home?token={{token}}\">\n                          {{baseScope.baseUrl}}/#/project/{{project_id}}/home\n                          </a> -->\n                          <a\n                          href=\"{{redirect_url}}\">\n                          {{baseScope.baseUrl}}/#/project/{{project_id}}/home\n                          </a>\n                        </div>\n                      </div>\n\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <!-- <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n\n                  <div style=\"margin-bottom: 10px; font-style: italic;\">Follow us on:</div>\n                    <div style=\"display: flex; flex-direction: row;\">\n\n                      <a href=\"https://www.facebook.com/tiledesk\" target=\"_blank\">\n                        <div\n                          style=\"border-color: #3d6ad6; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;\">\n                          <img\n                            src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/f762a3d7-541a-4b78-ae33-c35cdbe3b083\"\n                            width=\"20px\" height=\"20px\" />\n                        </div>\n                      </a>\n\n                      <a href=\"https://www.youtube.com/@tiledesk\" target=\"_blank\">\n                        <div\n                          style=\"border-color: #e7332f; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;\">\n                          <img\n                            src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/5dc5be2d-8bd3-43cf-b1ca-f12a36c01f1a\"\n                            width=\"20px\" height=\"20px\" />\n                        </div>\n                      </a>\n\n                      <a href=\"https://www.linkedin.com/company/tiledesk/\" target=\"_blank\">\n                        <div\n                          style=\"border-color: #1f77b5; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;\">\n                          <img\n                            src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/52d8909f-c847-4b44-8dfb-7cd041e481c3\"\n                            width=\"20px\" height=\"20px\" />\n                        </div>\n                      </a>\n\n                      <a href=\"https://www.instagram.com/tiledesk/\" target=\"_blank\">\n                        <div\n                          style=\"border-color: #f78881; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;\">\n                          <img\n                            src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/4c35afe2-277a-4fdd-8e50-0844148216c8\"\n                            width=\"20px\" height=\"20px\" />\n                        </div>\n                      </a>\n\n                      <a href=\"https://twitter.com/tiledesk\" target=\"_blank\">\n                        <div\n                          style=\"border-color: #2ea1f2; border: solid 1px; border-radius: 14px; padding: 4px; margin: 0px 6px; cursor: pointer;\">\n                          <img\n                            src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/3288635e-50b6-4b2d-bccc-0c9313dd11a5\"\n                            width=\"20px\" height=\"20px\" />\n                        </div>\n                      </a>\n\n                    </div>\n\n                </td>\n              </tr> -->\n\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n\n\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n\n</body>\n\n</html>"
  },
  {
    "path": "template/email/redirectToDesktopEmail_new.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n  <html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>Join Tiledesk from Desktop</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;\n      box-sizing: border-box;\n      font-size: 14px;\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n      background-color: #f6f6f6;\n      margin: 0;\n    }\n\n    .content {\n      width: 100%;\n      display: flex;\n      margin-top: 16px;\n    }\n\n    .inner-content {\n      width: 556px;\n    }\n\n    .content-box {\n      width: 100%;\n      background-color: white;\n      border: solid 1px #e9e9e9;\n      border-radius: 6px;\n    }\n\n    .header-image-container {\n      margin-top: 10px;\n      height: 40px;\n      background-image: url(\"https://tiledesk.com/wp-content/uploads/2022/08/tiledesk_v1-1.png\");\n      background-size: contain;\n      background-position: center;\n      background-repeat: no-repeat;\n    }\n\n    .title-container {\n      width: 100%;\n      display: flex;\n      margin-top: 20px;\n    }\n\n    .text-container {\n      padding: 20px 30px;\n      text-align: center;\n    }\n\n    .custom-divider {\n      width: 94%;\n      height: 1px;\n      border: none;\n      background-color: #cacaca;\n    }\n\n    .custom-button {\n      background-color: #ff8574 !important; \n      border: none; \n      color: white !important; \n      padding: 6px 18px; \n      text-align: center; \n      text-decoration: none; \n      display: inline-block; \n      font-size: 14px; \n      font-weight: 600; \n      letter-spacing: 1px; \n      margin: 4px 2px; \n      cursor: pointer; \n      border-radius: 8px;\n    }\n\n    .footer {\n      width: 100%;\n      text-align: center;\n      padding: 12px 0px\n    }\n\n    .powered-by-container {\n      display: flex; \n      padding: 20px 18px; \n      color: #888888; \n    }\n\n    .social-button-container {\n      display: flex; \n    }\n    \n    .social-icon-container {\n      border: solid 1px;\n      border-radius: 14px;\n      width: 20px;\n      height: 20px;\n      padding: 4px;\n      margin: 0px 6px;\n      cursor: pointer;\n    }\n\n    .unsubscribe-container {\n      align-items: center;\n      margin-top: 30px;\n    }\n\n    .site-link {\n      font-size: 12px;\n      font-weight: bold;\n      color: #999;\n    }\n\n    .unsubscribe-link {\n      font-size: 12px;\n      color: #999;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .inner-content {\n        width: auto;\n        padding: 0px 8px;\n      }\n\n      .content-box {\n        width: auto;\n      }\n\n      .text-container {\n        padding: 20px 10px;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\" bgcolor=\"#f6f6f6\">\n\n  <div class=\"content\" style=\"flex-direction: column;align-items: center;\">\n    <div class=\"inner-content\">\n      \n      <div class=\"content-box\">\n        <div class=\"header-image-container\"></div>\n        <div class=\"title-container\" style=\"justify-content: center;\">\n          <h2>Get Ready for a Full Tiledesk Experience!</h2>\n        </div>\n        <div class=\"text-container\">\n          For the optimal Tiledesk experience, we recommend using it on a desktop device. We've made\n          access easier for you with a personalized link.\n\n          <div style=\"margin-top: 20px; font-weight: 600;\">Access Tiledesk on your desktop now 👇</div>\n          <div style=\"margin-top: 10px;\">\n            <a href=\"{{baseScope.baseUrl}}/{{project_id}}/home?token={{token}}\" class=\"custom-button\" style=\"color: white;\">\n              Enjoy Tiledesk\n            </a>\n          </div>\n\n          <div style=\"margin-top: 40px; text-align: left;\">\n            Alternatively, simply copy and paste this URL into your browser:\n            <div>\n              <a\n                href=\"{{baseScope.baseUrl}}/{{project_id}}/home?token={{token}}\">\n                {{baseScope.baseUrl}}/{{project_id}}/home\n              </a>\n            </div>\n          </div>\n        </div>\n\n        <hr class=\"custom-divider\">\n        \n        <div class=\"powered-by-container\" style=\"align-items: center;\">\n          <span>Powered by </span>\n          <span style=\"display: flex;\"><img\n              src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n              height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n          <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n        </div>\n        \n      </div>\n      \n      <div class=\"footer\">\n        <div style=\"margin-bottom: 10px; font-style: italic;\">Follow us on:</div>\n        <div class=\"social-button-container\" style=\"flex-direction: row; justify-content: center;\">\n          \n          <!-- Facebook -->\n          <a href=\"https://www.facebook.com/tiledesk\" target=\"_blank\">\n            <div class=\"social-icon-container\" style=\"border-color: #3d6ad6;\">\n              <img\n                src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/f762a3d7-541a-4b78-ae33-c35cdbe3b083\"\n                width=\"20px\" height=\"20px\" />\n            </div>\n          </a>\n\n          <!-- YouTube -->\n          <a href=\"https://www.youtube.com/@tiledesk\" target=\"_blank\">\n            <div class=\"social-icon-container\" style=\"border-color: #e7332f;\">\n              <img\n                src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/5dc5be2d-8bd3-43cf-b1ca-f12a36c01f1a\"\n                width=\"20px\" height=\"20px\" />\n            </div>\n          </a>\n\n          <!-- Linkedin -->\n          <a href=\"https://www.linkedin.com/company/tiledesk/\" target=\"_blank\">\n            <div class=\"social-icon-container\" style=\"border-color: #1f77b5;\">\n              <img\n                src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/52d8909f-c847-4b44-8dfb-7cd041e481c3\"\n                width=\"20px\" height=\"20px\" />\n            </div>\n          </a>\n\n          <!-- Instagram -->\n          <a href=\"https://www.instagram.com/tiledesk/\" target=\"_blank\">\n            <div class=\"social-icon-container\" style=\"border-color: #f78881;\">\n              <img\n                src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/4c35afe2-277a-4fdd-8e50-0844148216c8\"\n                width=\"20px\" height=\"20px\" />\n            </div>\n          </a>\n\n          <!-- Twitter -->\n          <a href=\"https://twitter.com/tiledesk\" target=\"_blank\">\n            <div class=\"social-icon-container\" style=\"border-color: #2ea1f2;\">\n              <img\n                src=\"https://github.com/Tiledesk/tiledesk-dashboard/assets/45603238/3288635e-50b6-4b2d-bccc-0c9313dd11a5\"\n                width=\"20px\" height=\"20px\" />\n            </div>\n          </a>\n          \n        </div>\n\n        <div class=\"unsubscribe-container\" style=\"display: flex;flex-direction: column;\">\n          <a href=\"http://www.tiledesk.com\" class=\"site-link\">Tiledesk.com </a>\n          <a href=\"%unsubscribe_url%\" class=\"unsubscribe-link\">Unsubscribe</a>\n        </div>\n\n      </div>      \n    </div>\n  </div>\n\n</body>"
  },
  {
    "path": "template/email/resetPassword.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New email from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n          <!--\n          <div style=\"text-align:center\">\n            <a href=\"http://www.tiledesk.com\"\n              style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n              <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" class=\"CToWUd\">\n            </a>\n          </div>\n          -->\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <h2 style=\"text-align: center; letter-spacing: 1px; \">\n                        Password reset request\n                      </h2>\n\n                      <br>\n                      <br<strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                        Hi {{userFirstname}} {{userLastname}},</strong>\n\n                        <br> <br>\n                        Seems like you forgot your password for Tiledesk. If this is true, click below to reset your\n                        password\n                        <div style=\"text-align: center;\">\n                          <br><br>\n                          <a href=\"{{baseScope.baseUrl}}/#/resetpassword/{{resetPswRequestId}}\"\n                            style=\" background-color: #ff8574 !important; border: none; color: white; padding: 12px 30px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; font-weight: 600; letter-spacing: 1px; margin: 4px 2px; cursor: pointer;\">\n                            Reset My Password\n                          </a>\n                        </div>\n                        <br><br>If you didn't forget your password you can safely ignore this email.\n                        <br><br><span style=\"font-size:12px; \">If you're having trouble clicking the \"Reset My Password\"\n                          button, copy and paste the URL below into your web browser: </span>\n                        <br>\n                        <span\n                          style=\"color:#03a5e8; font-size:12px; \">{{baseScope.baseUrl}}/#/resetpassword/{{resetPswRequestId}}</span>\n                        <br><br> The Tiledesk Team\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/sendTranscript.html",
    "content": "<!DOCTYPE html\n  PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>Request transcript</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <div style=\"text-align:center\">\n                <!--\n                     <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                       <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\" style=\"max-width:200px;outline:none;text-decoration:none;border:none;height:auto;margin-left:0px;\" class=\"CToWUd\">\n                     </a>\n                     -->\n              </div>\n            </tr>\n\n\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      <!-- <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">-->\n                      Hi,\n                      <!-- </strong> -->\n                      <br><br> here's the transcript of your conversation.\n\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      Chat started on <span\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{formattedCreatedAt}}</span>\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      <!-- <strong style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\"> -->\n\n\n\n\n                      {{{transcriptAsHtml}}}\n\n\n\n\n                      <!-- </strong> -->\n                    </td>\n                  </tr>\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n\n\n\n\n\n                </table>\n\n              </td>\n            </tr>\n\n\n\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n\n\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      Department : <span\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.department.name}}</span>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      Source : <span\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.sourcePage}}\n                      </span>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      User Agent : <span\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.userAgent}}</span>\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 0 0 20px;\"\n                      valign=\"top\">\n                      Language : <span\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{request.language}}</span>\n                    </td>\n                  </tr>\n\n\n\n                </table>\n\n\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0; padding: 0 0 20px;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                </td>\n              </tr>\n            </table>\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/test.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\" style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n  <body>     \n   Hi {{user.name}}, this is a test email from Tiledesk\n\n   {{#if enabled}}\n    <h1>{{name}}</h1>\n   {{/if}}\n\n  </body>\n</html>"
  },
  {
    "path": "template/email/ticket.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New Ticket from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  {{#if baseScope.replyEnabled}}\n  <div>\\# Please type your reply above this line \\#</div>\n  {{/if}}\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n\n\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n\n                <div style=\"text-align:center\">\n                  <!--\n                  <a href=\"http://www.tiledesk.com\"\n                    style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                    <img src=\"https://tiledesk.com/wp-content/uploads/2023/01/tiledesk_log_email_200.png\"\n                      class=\"CToWUd\">\n                  </a>\n                  -->\n                </div>\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <td class=\"alert alert-warning\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top; font-weight: 500; text-align: center; border-radius: 3px 3px 0 0; margin: 0;\"\n                align=\"center\" valign=\"top\">\n                <div>\n                  <h2>Ticket number: #{{message.request.ticket_id}}</h2>\n                </div>\n\n              </td>\n            </tr>\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      Sender: <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">{{message.senderFullname}}</strong>\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      {{#ifEquals message.type \"text\"}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"image\"}}\n                      <img src=\"{{message.metadata.src}}\" />\n                      {{#if msgText}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/if}}\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"file\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{/ifEquals}}\n                      {{#ifEquals message.type \"frame\"}}\n                      <a href=\"{{message.metadata.src}}\">{{message.metadata.name}}</a>\n                      {{#if msgText}}\n                      <div style=\"white-space: pre-wrap;\">{{{msgText}}}</div>\n                      {{/if}}\n                      {{/ifEquals}}\n\n                      {{#if message.attributes}}\n                      {{#if message.attributes.attachment}}\n                      {{#if message.attributes.attachment.buttons}}\n                      {{#each message.attributes.attachment.buttons}}\n                      {{#ifEquals this.type \"url\"}}\n                      <li><a href=\"{{this.link}}\" class=\"dynamic_button\">{{this.value}}</a></li>\n                      {{else}}\n                      <li><a\n                          href=\"mailto:{{../message.request.request_id}}@{{../baseScope.inboundDomain}}?subject=Re:%20{{../message.request.subject}}&body={{this.value}}\"\n                          class=\"dynamic_button\">{{this.value}}</a></li>\n                      {{/ifEquals}}\n                      {{/each}}\n\n\n                      {{/if}}\n                      {{/if}}\n                      {{/if}}\n\n                      <!-- {{#if message.attributes.intent_info}}\n  {{#if message.attributes.intent_info.others}}\n    Others:\n    {{#each message.attributes.intent_info.others}}\n      <li><span>{{this.answer}}</span></li>\n    {{/each}}\n  {{/if}}\n{{/if}} -->\n\n\n\n\n\n                    </td>\n                  </tr>\n\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n\n                      Click <a href=\"{{seamlessPage}}{{tokenQueryString}}\">here</a> to continue by chat the\n                      conversation.\n                    </td>\n                  </tr>\n\n\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <!-- <br><span><a href=\"%unsubscribe_url%\"  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>  -->\n                </td>\n              </tr>\n            </table>\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n</body>\n\n</html>"
  },
  {
    "path": "template/email/ticket.txt",
    "content": "\\# Please type your reply above this line \\#\n\nTicket number: #{{message.request.ticket_id}}\n\n{{message.senderFullname}}\n\n{{message.text}}\n\nContinue the conversation by chat here: {{seamlessPage}}{{tokenQueryString}}\n\nPowered by Tiledesk : https://tiledesk.com"
  },
  {
    "path": "template/email/verify.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html lang=\"en\" xmlns=\"http://www.w3.org/1999/xhtml\"\n  style=\"font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n<head>\n  <meta name=\"viewport\" content=\"width=device-width\" />\n  <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n  <title>New email from Tiledesk</title>\n\n  <style type=\"text/css\">\n    img {\n      max-width: 100%;\n      margin-left: 16px;\n      text-align: center !important;\n    }\n\n    img.CToWUd {\n      margin-bottom: 16px;\n      max-width: 200px !important;\n      width: 200px !important;\n      min-width: 200px !important;\n      outline: none;\n      text-decoration: none;\n      border: none;\n      height: auto;\n      margin-left: 0px;\n    }\n\n    body {\n      -webkit-font-smoothing: antialiased;\n      -webkit-text-size-adjust: none;\n      width: 100% !important;\n      height: 100%;\n      line-height: 1.6em;\n    }\n\n    body {\n      background-color: #f6f6f6;\n    }\n\n    @media only screen and (max-width: 640px) {\n      body {\n        padding: 0 !important;\n      }\n\n      h1 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n        text-align: center !important;\n      }\n\n      h2 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h3 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h4 {\n        font-weight: 800 !important;\n        margin: 20px 0 5px !important;\n      }\n\n      h1 {\n        font-size: 22px !important;\n      }\n\n      h2 {\n        font-size: 18px !important;\n      }\n\n      h3 {\n        font-size: 16px !important;\n      }\n\n      .container {\n        padding: 0 !important;\n        width: 100% !important;\n      }\n\n      .content {\n        padding: 0 !important;\n      }\n\n      .content-wrap {\n        padding: 10px !important;\n      }\n\n      .invoice {\n        width: 100% !important;\n      }\n    }\n  </style>\n</head>\n\n<body itemscope itemtype=\"http://schema.org/EmailMessage\"\n  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; -webkit-font-smoothing: antialiased; -webkit-text-size-adjust: none; width: 100% !important; height: 100%; line-height: 1.6em; background-color: #f6f6f6; margin: 0;\"\n  bgcolor=\"#f6f6f6\">\n\n  <table class=\"body-wrap\"\n    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; background-color: #f6f6f6; margin: 0;\"\n    bgcolor=\"#f6f6f6\">\n    <tr\n      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n      <td class=\"container\" width=\"600\"\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; display: block !important; max-width: 600px !important; clear: both !important; margin: 0 auto;\"\n        valign=\"top\">\n        <div class=\"content\"\n          style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; max-width: 600px; display: block; margin: 0 auto; padding: 20px;\">\n          <table class=\"main\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; border-radius: 3px; background-color: #fff; margin: 0; border: 1px solid #e9e9e9;\"\n            bgcolor=\"#fff\">\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n              <div style=\"text-align:center\">\n                <!--\n                      <a href=\"http://www.tiledesk.com\" style=\"color:#2daae1;font-weight:bold;text-decoration:none;word-break:break-word\" target=\"_blank\">\n                        <img src=\"https://tiledesk.com/tiledesk-logo.png\" class=\"CToWUd\">\n                      </a>\n                      -->\n              </div>\n            </tr>\n\n\n            <!-- <tr style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n    \n                     <td class=\"alert alert-warning\" style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 16px; vertical-align: top;  font-weight: 500; text-align: center; border-radius: 3px 3px 0 0;  margin: 0;\" align=\"center\"; valign=\"top\">\n                       <div>\n                         <h2>Welcome</h2>\n                       </div>\n    \n                     </td>\n                  </tr> -->\n\n\n            <tr\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <td class=\"content-wrap\"\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0; padding: 20px;\"\n                valign=\"top\">\n                <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                      <strong\n                        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">Hi\n                        {{savedUser.firstname}} {{savedUser.lastname}},</strong>\n                      <!-- <br> welcome on Tiledesk.com. -->\n                      <br><br> Thank you for signin up with Tiledesk.\n                      <br><br> To complete the setup, <span><a\n                          href=\"{{baseScope.baseUrl}}/#/verify/email/{{savedUser._id}}/{{code}}\"> click here to verify your email\n                          address. </a> </span>\n                      <br><br>Give us your feedback! We need your advice. Send an email to <a\n                        href=\"mailto:info@tiledesk.com\">info@tiledesk.com</a>\n                      <br><br> The Tiledesk Team\n                    </td>\n                  </tr>\n\n                  <tr\n                    style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                    <td class=\"content-block\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n                      valign=\"top\">\n                    </td>\n                  </tr>\n                </table>\n              </td>\n            </tr>\n\n            <tr>\n              <td>\n                <hr style=\"width:94%;height:1px;border:none;background-color: #cacaca;\">\n\n                <div style=\"display: flex; padding: 20px 18px; color: #888888; align-items: center;\">\n                  <span>Powered by </span>\n                  <span style=\"display: flex;\"><img\n                      src=\"https://tiledesk.com/wp-content/uploads/2023/05/tiledesk-solo_logo_new_gray.png\" width=\"15\"\n                      height=\"15\" style=\"margin-left: 6px; margin-top: 2px;\" /></span>\n                  <span style=\"font-weight: bold; margin-left: 2px;\">Tiledesk</span>\n                </div>\n\n              </td>\n            </tr>\n\n            \n          </table>\n          <div class=\"footer\"\n            style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; width: 100%; clear: both; color: #999; margin: 0; padding: 20px;\">\n            <table width=\"100%\"\n              style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n              <tr\n                style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; margin: 0;\">\n                <td class=\"aligncenter content-block\"\n                  style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; vertical-align: top; color: #999; text-align: center; margin: 0;\"\n                  align=\"center\" valign=\"top\">\n                  <span><a href=\"http://www.tiledesk.com\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">\n                      Tiledesk.com </a></span>\n                  <br><span><a href=\"%unsubscribe_url%\"\n                      style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 12px; color: #999; text-decoration: underline; margin: 0;\">Unsubscribe</a></span>\n                </td>\n              </tr>\n            </table>\n          </div>\n        </div>\n      </td>\n      <td\n        style=\"font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif; box-sizing: border-box; font-size: 14px; vertical-align: top; margin: 0;\"\n        valign=\"top\"></td>\n    </tr>\n  </table>\n</body>\n\n</html>"
  },
  {
    "path": "test/app-test.js",
    "content": "process.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical'\n\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nconst userService = require('../services/userService');\nconst projectService = require('../services/projectService');\n\nlet log = false;\n\nlet expect = chai.expect;\nlet assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('Root Test', () => {\n\n    /**\n     * Try to perform a request with an invalid id project.\n     * A fake id is passed.\n     * This test must respond with status 400 and with an error.\n     */\n    it(\"Wrong request url\", (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n            projectService.create(\"test-root-project\", savedUser._id).then((savedProject) => {\n\n                let fake_id_project = \"fake1234\"\n\n                chai.request(server)\n                    //.get('/' + savedUser._id + '/faq_kb')\n                    .get('/' + fake_id_project + '/faq_kb')\n                    .auth(email, pwd)\n                    .end((err, res) => {\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n                        \n                        res.should.have.status(400);\n                        assert.notEqual(res.body.error, null);\n                        expect(res.body.error).to.equal(\"Invalid project id: \" + fake_id_project)\n                        done();\n                    })\n            })\n        })\n    }).timeout(5000)\n\n    /**\n     * Try to perform a request with an invalid id project.\n     * A non existent mongo id (like user id) is passed.\n     * This test must respond with status 400 and with an error.\n     */\n    it(\"Wrong request url\", (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n            projectService.create(\"test-root-project\", savedUser._id).then((savedProject) => {\n\n                // A real valid mongo id without a corresponding project\n                let fake_id_project = savedUser._id;\n\n                chai.request(server)\n                    //.get('/' + savedUser._id + '/faq_kb')\n                    .get('/' + fake_id_project + '/faq_kb')\n                    .auth(email, pwd)\n                    .end((err, res) => {\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n                        \n                        res.should.have.status(400);\n                        assert.notEqual(res.body.error, null);\n                        expect(res.body.error).to.equal(\"Project not found with id: \" + fake_id_project)\n                        done();\n                    })\n            })\n        })\n    }).timeout(5000)\n\n})"
  },
  {
    "path": "test/authentication.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.ADMIN_EMAIL = \"admin@tiledesk.com\";\nprocess.env.LOG_LEVEL = 'critical';\n//var User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\nvar jwt = require('jsonwebtoken');\n\nvar config = require('../config/database');\n\nlet log = false;\n\nvar mongoose = require('mongoose');\nmongoose.connect(config.databasetest);\n\n\nchai.use(chaiHttp);\n\ndescribe('Authentication', () => {\n\n    // mocha test/authentication.js  --grep 'signinOk'\n\n    describe('/signin', () => {\n\n\n        it('signinOk', (done) => {\n\n            var email = \"test-signin-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .post('/auth/signin')\n                    .send({ \"email\": email, \"password\": pwd })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.success).to.equal(true);\n                        expect(res.body.token).to.not.equal(null);\n                        expect(res.body.user.email).to.equal(email);\n                        expect(res.body.user.password).to.equal(undefined);\n\n                        done();\n                    });\n\n            });\n        });\n\n\n        it('signinkO', (done) => {\n\n            var email = \"test-signinko-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            chai.request(server)\n                .post('/auth/signin')\n                .send({ \"email\": email, \"password\": pwd })\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(401);\n\n                    done();\n                });\n\n        });\n\n\n        it('signinValidation', (done) => {\n\n            var email = \"test-signinko-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            chai.request(server)\n                .post('/auth/signin')\n                .send()\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(422);\n\n                    done();\n                });\n\n        });\n\n\n        // mocha test/authentication.js  --grep 'signinLowercase'\n        it('signinLowercase', (done) => {\n\n            var email = \"Test-SigninKO-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .post('/auth/signin')\n                    .send({ \"email\": email, \"password\": pwd })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.success).to.equal(true);\n                        expect(res.body.token).to.not.equal(null);\n                        expect(res.body.user.email).to.equal(email.toLowerCase());\n                        expect(res.body.user.password).to.equal(undefined);\n\n                        chai.request(server)\n                            .get('/users/')\n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                done();\n                            });\n                    });\n            });\n        });\n\n    });\n\n    describe('/signup', () => {\n\n\n        it('signupOk', (done) => {\n\n            var email = \"test-signuook-\" + Date.now() + \"@email.com\";\n            var pwd = \"Pwd1234!\";\n\n            chai.request(server)\n                .post('/auth/signup')\n                .send({ email: email, password: pwd, lastname: \"lastname\", firstname: \"firstname\", disableEmail: true }) // whi disableEmail true?\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(200);\n                    res.body.should.be.a('object');\n                    expect(res.body.success).to.equal(true);\n                    expect(res.body.user.email).to.equal(email);\n                    expect(res.body.user.password).to.equal(undefined);\n\n                    done();\n                });\n        });\n\n\n        // it('verifyemail', (done) => {\n\n        //     let user_id = \"670e55c8187b430e793d644e\";\n        //     let code = \"4fx6e1hfcm2admb4a\";\n        //     chai.request(server)\n        //         .put('/auth/verifyemail/' + user_id + '/' + code)\n        //         .send({ emailVerified: true })\n        //         .end((err, res) => {\n\n        //             console.error(\"err: \", err)\n        //             console.log(\"res.body: \", res.body)\n        //             done();\n        //         })\n\n\n\n        // });\n\n\n\n\n        // it('signUpAdminNoVerificationEmail', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     chai.request(server)\n        //         .post(\"/auth/signin\")\n        //         .send({ email: \"admin@tiledesk.com\", password: \"adminadmin\" })\n        //         .end((err, res) => {\n\n        //             // console.log(\"login with superadmin res.body: \", res.body)\n        //             let superadmin_token = res.body.token;\n\n        //             chai.request(server)\n        //                 .post(\"/auth/signup\")\n        //                 .set('Authorization', superadmin_token)\n        //                 .send({ email: email, password: pwd, lastname: \"lastname\", firstname: \"firstname\", disableEmail: true })\n        //                 .end((err, res) => {\n\n        //                     // console.log(\"res.body: \", res.body);\n        //                     done();\n        //                 })\n        //         })\n\n\n        // })\n\n        // mocha test/authentication.js  --grep 'signupUpperCaseEmail'\n\n\n        it('signupUpperCaseEmail', (done) => {\n\n            var now = Date.now();\n            var email = \"test-signupUpperCaseEmail-\" + now + \"@email.com\";\n            var pwd = \"Pwd1234!\";\n\n            chai.request(server)\n                .post('/auth/signup')\n                .send({ email: email, password: pwd, lastname: \"lastname\", firstname: \"firstname\", disableEmail: true })\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(200);\n                    res.body.should.be.a('object');\n                    expect(res.body.success).to.equal(true);\n                    expect(res.body.user.email).to.equal(\"test-signupuppercaseemail-\" + now + \"@email.com\");\n                    expect(res.body.user.password).to.equal(undefined);\n\n                    done();\n                });\n        });\n\n        // mocha test/authentication.js  --grep 'signupkOWrongEmail'\n        it('signupkOWrongEmail', (done) => {\n\n            var email = \"test-signuoOk-\" + Date.now() + \"@email\";\n            var pwd = \"Pwd1234!\";\n\n            chai.request(server)\n                .post('/auth/signup')\n                .send({ email: email, password: pwd, lastname: \"lastname\", firstname: \"firstname\", disableEmail: true })\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(422);\n\n                    done();\n                });\n        });\n\n\n        // });\n\n\n\n\n\n\n\n\n\n\n\n\n\n    });\n\n    describe('/signInAnonymously', () => {\n\n\n        it('signInAnonymouslyOk', (done) => {\n\n            var email = \"test-signInAnonymouslyOk-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signInAnonymouslyOk\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/auth/signinAnonymously')\n                        .send({ id_project: savedProject._id, email: \"email@email.com\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(true);\n                            expect(res.body.user.email).to.equal(\"email@email.com\");\n                            expect(res.body.token).to.not.equal(undefined);\n\n                            done();\n                        });\n                });\n            });\n        });\n\n\n        // it('signInAnonymouslyReLoginSameProject', (done) => {\n\n\n        //     var email = \"test-signInAnonymouslyReLogin-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n        //         // create(name, createdBy, settings)\n        //         projectService.create(\"test-signInAnonymouslyReLogin\", savedUser._id).then(function(savedProject) {     \n\n        //                 chai.request(server)\n        //                     .post('/auth/signinAnonymously' )\n        //                     .send({ id_project: savedProject._id, email: \"email@email.com\"})\n        //                     .end((err, res) => {\n        //                         //console.log(\"res\",  res);\n        //                         console.log(\"res.body\",  res.body);\n        //                         res.should.have.status(200);\n        //                         res.body.should.be.a('object');\n        //                         expect(res.body.success).to.equal(true);                                                                                                                     \n        //                         expect(res.body.user.email).to.equal(\"email@email.com\");                                               \n        //                         expect(res.body.token).to.not.equal(undefined);                                               \n        //                         expect(res.body.user._id).to.not.equal(undefined);                                               \n\n        //                         var uuid = res.body.user._id.toString();\n        //                         console.log(\"uuid\", uuid);\n\n        //                         var token = res.body.token;\n        //                         console.log(\"token\", token);\n\n        //                         chai.request(server)\n        //                             .post('/auth/resigninAnonymously' )\n        //                             .set('Authorization', token)\n        //                             .send({ id_project: savedProject._id, email: \"email@email.com\"})\n        //                             .end((err, res) => {\n        //                                 //console.log(\"res\",  res);\n        //                                 console.log(\"res.body\",  res.body);\n        //                                 res.should.have.status(200);\n        //                                 res.body.should.be.a('object');\n        //                                 expect(res.body.success).to.equal(true);                                                                                                                     \n        //                                 expect(res.body.user.email).to.equal(\"email@email.com\");                                               \n        //                                 expect(res.body.token).to.not.equal(undefined);                                               \n        //                                 expect(res.body.user._id.toString()).to.equal(uuid.toString());                                               \n\n        //                                 done();\n        //                             });\n        //                     });\n        //                 }); \n        //             });\n\n\n        // });\n\n\n\n\n\n        // it('signInAnonymouslyReLoginDifferentProject', (done) => {\n\n\n        //     var email = \"test-signInAnonymouslyReLogin-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n        //         // create(name, createdBy, settings)\n        //         projectService.create(\"test-signInAnonymouslyReLogin\", savedUser._id).then(function(savedProject) {     \n\n        //             projectService.create(\"test-signInAnonymouslyReLoginDifferent\", savedUser._id).then(function(savedProjectDifferent) {     \n\n        //                 chai.request(server)\n        //                     .post('/auth/signinAnonymously' )\n        //                     .send({ id_project: savedProject._id, email: \"email@email.com\"})\n        //                     .end((err, res) => {\n        //                         //console.log(\"res\",  res);\n        //                         console.log(\"res.body\",  res.body);\n        //                         res.should.have.status(200);\n        //                         res.body.should.be.a('object');\n        //                         expect(res.body.success).to.equal(true);                                                                                                                     \n        //                         expect(res.body.user.email).to.equal(\"email@email.com\");                                               \n        //                         expect(res.body.token).to.not.equal(undefined);                                               \n        //                         expect(res.body.user._id).to.not.equal(undefined);                                               \n\n        //                         var uuid = res.body.user._id.toString();\n        //                         console.log(\"uuid\", uuid);\n\n        //                         var token = res.body.token;\n        //                         console.log(\"token\", token);\n\n        //                         chai.request(server)\n        //                             .post('/auth/resigninAnonymously' )\n        //                             .set('Authorization', token)\n        //                             .send({ id_project: savedProjectDifferent._id, email: \"email@email.com\"})\n        //                             .end((err, res) => {\n        //                                 //console.log(\"res\",  res);\n        //                                 console.log(\"res.body\",  res.body);\n        //                                 res.should.have.status(200);\n        //                                 res.body.should.be.a('object');\n        //                                 expect(res.body.success).to.equal(true);                                                                                                                     \n        //                                 expect(res.body.user.email).to.equal(\"email@email.com\");                                               \n        //                                 expect(res.body.token).to.not.equal(undefined);                                               \n        //                                 expect(res.body.user._id.toString()).to.equal(uuid.toString());                                               \n\n        //                                 done();\n        //                             });\n        //                     });\n        //                 }); \n        //             });\n        //         });\n\n        // });\n\n    });\n\n    describe('/signinWithCustomToken', () => {\n\n\n        it('signinWithCustomTokenOk', (done) => {\n\n            var email = \"test-signinwithcustomtoken-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomToken\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { _id: \"123\", firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\", customAttr: \"c1\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.user.email).to.equal(\"email2@email.com\");\n                                    expect(res.body.user.firstname).to.equal(\"andrea\");\n                                    expect(res.body.user.customAttr).to.equal(\"c1\");\n                                    expect(res.body.token).to.not.equal(undefined);\n                                    expect(res.body.token).to.equal('JWT ' + jwtToken);\n\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        it('signinWithCustomTokenKO', (done) => {\n\n            var email = \"test-signinwithcustomtoken-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomTokenKO\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            var externalUserObj = { _id: \"123\", name: \"andrea\", surname: \"leo\", customAttr: \"c1\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret + \"1234567KOOOOOOO\", signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                .send({ id_project: savedProject._id })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(401);\n\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        it('signinWithCustomTokenKONoID', (done) => {\n\n            var email = \"test-signinwithcustomtokenkonoid-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomTokenKONoID\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(401);\n\n                                    done();\n                                });\n                        });\n                });\n            });\n\n        }).timeout(20000);\n\n\n        // mocha test/authentication.js  --grep 'signinWithCustomTokenKONoAud'\n        it('signinWithCustomTokenKONoAud', (done) => {\n\n            var email = \"test-signinwithcustomtokenkowrongaud-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomTokenKOWrongAud\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { _id: 1234, firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                //audience:  'https://tiledesk.com/projects/'+savedProject._id ,                                              \n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(401);\n\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/authentication.js  --grep 'signinWithCustomTokenOkTwoSigninWithCT'\n        it('signinWithCustomTokenOkTwoSigninWithCT', (done) => {\n\n            var email = \"test-signinwithcustomtokenoktwosigninwithct-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomTokenOkTwoSigninWithCT\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { _id: \"123\", firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\", customAttr: \"c1\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.user.email).to.equal(\"email2@email.com\");\n                                    expect(res.body.user.firstname).to.equal(\"andrea\");\n                                    expect(res.body.user.customAttr).to.equal(\"c1\");\n                                    expect(res.body.token).to.not.equal(undefined);\n                                    expect(res.body.token).to.equal('JWT ' + jwtToken);\n\n                                    chai.request(server)\n                                        .post('/auth/signinWithCustomToken')\n                                        .set('Authorization', 'JWT ' + jwtToken)\n                                        //.send({ id_project: savedProject._id})\n                                        .send()\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(true);\n                                            expect(res.body.user.email).to.equal(\"email2@email.com\");\n                                            expect(res.body.user.firstname).to.equal(\"andrea\");\n                                            expect(res.body.user.customAttr).to.equal(\"c1\");\n                                            expect(res.body.token).to.not.equal(undefined);\n                                            expect(res.body.token).to.equal('JWT ' + jwtToken);\n\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n\n        }).timeout(20000);\n\n\n        // mocha test/authentication.js  --grep 'signinWithCustomTokenRoleNew'\n        it('signinWithCustomTokenRoleNew', (done) => {\n\n            var email = \"test-signinWithCustomTokenRole-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n            var emailToCheck = \"emailrole\" + Date.now() + \"@email.com\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                // create(name, createdBy, settings)\n                projectService.create(\"test-signinWithCustomTokenRole\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { _id: \"123\", firstname: \"andrea\", lastname: \"leo\", email: emailToCheck, role: \"admin\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.user.email).to.equal(emailToCheck);\n                                    expect(res.body.user.firstname).to.equal(\"andrea\");\n                                    // expect(res.body.user._id).to.not.equal(\"123\");\n                                    expect(res.body.token).to.not.equal(undefined);\n                                    // expect(res.body.token).to.equal('JWT '+jwtToken);  \n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/authentication.js  --grep 'signinWithCustomTokenRole'\n        it('signinWithCustomTokenRoleEmailAlreadyUsed', (done) => {\n\n            var email = \"test-signinWithCustomTokenRoleEmailAlreadyUsed-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n            var emailToCheck = \"emailrole\" + Date.now() + \"@email.com\";\n\n            userService.signup(emailToCheck, pwd, \"andrea\", \"leo\").then(function (savedUserToCheck) {\n                userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                    projectService.create(\"test-signinWithCustomTokenRoleEmailAlreadyUsed\", savedUser._id).then(function (savedProject) {\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/keys/generate')\n                            .auth(email, pwd)\n                            .send()\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n                                expect(res.body.jwtSecret).to.not.equal(null);\n\n                                // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                                var externalUserObj = { _id: \"123\", firstname: \"andrea\", lastname: \"leo\", email: emailToCheck, role: \"admin\" };\n                                if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                                var signOptions = {\n                                    subject: 'userexternal',\n                                    audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                                };\n\n                                var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                                if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                                chai.request(server)\n                                    .post('/auth/signinWithCustomToken')\n                                    .set('Authorization', 'JWT ' + jwtToken)\n                                    //.send({ id_project: savedProject._id})\n                                    .send()\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(true);\n                                        expect(res.body.user.email).to.equal(emailToCheck);\n                                        expect(res.body.user.firstname).to.equal(\"andrea\");\n                                        // expect(res.body.user._id).to.not.equal(\"123\");\n                                        expect(res.body.token).to.not.equal(undefined);\n                                        // expect(res.body.token).to.equal('JWT '+jwtToken);\n                                        done();\n                                    });\n                            });\n                    });\n                });\n            });\n        });\n\n\n        // mocha test/authentication.js  --grep 'signinWithCustomTokenRoleSameOwnerEmail'\n        it('signinWithCustomTokenRoleSameOwnerEmail', (done) => {\n\n            var email = \"test-sctrolesameowner-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n            var emailToCheck = email;\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-signinWithCustomTokenRoleEmailAlreadyUsed\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);\n\n                            // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                            var externalUserObj = { _id: \"123\", firstname: \"andrea\", lastname: \"leo\", email: emailToCheck, role: \"admin\" };\n                            if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                            var signOptions = {\n                                subject: 'userexternal',\n                                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                            };\n\n                            var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                            if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                            chai.request(server)\n                                .post('/auth/signinWithCustomToken')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                //.send({ id_project: savedProject._id})\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.user.email).to.equal(emailToCheck);\n                                    expect(res.body.user.firstname).to.equal(\"Test Firstname\");\n                                    // expect(res.body.user._id).to.not.equal(\"123\");          \n                                    expect(res.body.token).to.not.equal(undefined);\n                                    // expect(res.body.token).to.equal('JWT '+jwtToken);  \n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n    }).timeout(20000);\n\n});"
  },
  {
    "path": "test/authenticationJwt.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\n//var User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\nvar jwt = require('jsonwebtoken');\nvar config = require('../config/database'); // get db config file\nvar faqService = require('../services/faqService');\n\nlet log = false;\n\nchai.use(chaiHttp);\n\ndescribe('AuthenticationJWT', () => {\n\n    it('signinJWt-userNoAudNoSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                var jwtToken = jwt.sign(savedUser.toObject(), config.secret);\n                if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                chai.request(server)\n                    .get('/' + savedProject._id + '/requests')\n                    .set('Authorization', 'JWT ' + jwtToken)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n\n                        done();\n                    });\n            });\n        });\n    });\n\n\n    it('signinJWt-userYESAudNoSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                var savedUserObj = savedUser.toObject();\n                if (log) { console.log(\"savedUserObj\", savedUserObj); }\n\n                var signOptions = {\n                    // subject:  'user',                                                                 \n                    audience: 'https://tiledesk.com',\n                };\n\n\n                var jwtToken = jwt.sign(savedUserObj, config.secret, signOptions);\n                if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                chai.request(server)\n                    .get('/' + savedProject._id + '/requests')\n                    .set('Authorization', 'JWT ' + jwtToken)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n\n                        done();\n                    });\n            });\n        });\n    });\n                \n\n\n    it('signinJWt-userYESAudYesSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                var savedUserObj = savedUser.toObject();\n                if (log) { console.log(\"savedUserObj\", savedUserObj); }\n\n                var signOptions = {\n                    subject: 'user',\n                    audience: 'https://tiledesk.com',\n                };\n\n                var jwtToken = jwt.sign(savedUserObj, config.secret, signOptions);\n                if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                chai.request(server)\n                    .get('/' + savedProject._id + '/requests')\n                    .set('Authorization', 'JWT ' + jwtToken)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n\n                        done();\n                    });\n            });\n        });\n    });\n\n\n    it('signinJWt-Project-user-YESAudNoSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/keys/generate')\n                    .auth(email, pwd)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.jwtSecret).to.not.equal(null);\n\n                        var savedUserObj = savedUser.toObject();\n                        if (log) { console.log(\"savedUserObj\", savedUserObj); }\n\n                        var signOptions = {\n                            // subject:  'user',                                                                 \n                            audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                        };\n\n                        var jwtToken = jwt.sign(savedUserObj, res.body.jwtSecret, signOptions);\n                        if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                        chai.request(server)\n                            .get('/' + savedProject._id + '/requests')\n                            .set('Authorization', 'JWT ' + jwtToken)\n                            .send()\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n\n                                done();\n                            });\n                    });\n            });\n        });\n    });\n                            \n                    \n\n\n    it('signinJWt-Project-user-YESAudYesSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/keys/generate')\n                    .auth(email, pwd)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.jwtSecret).to.not.equal(null);\n\n                        var savedUserObj = savedUser.toObject();\n                        if (log) { console.log(\"savedUserObj\", savedUserObj); }\n\n                        var signOptions = {\n                            subject: 'user',\n                            audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                        };\n\n                        var jwtToken = jwt.sign(savedUserObj, res.body.jwtSecret, signOptions);\n                        if (log) { console.log(\"jwtToken\", jwtToken); }\n                        \n                        chai.request(server)\n                            .get('/' + savedProject._id + '/requests')\n                            .set('Authorization', 'JWT ' + jwtToken)\n                            .send()\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n\n                                done();\n                            });\n                    });\n            });\n        });\n    });                    \n                                                \n\n    it('signinJWt-Project-external-user-YESAudYesSubject', (done) => {\n\n\n        //   this.timeout();\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            // create(name, createdBy, settings)\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/keys/generate')\n                    .auth(email, pwd)\n                    .send()\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.jwtSecret).to.not.equal(null);\n\n\n                        var externalUserObj = { _id: \"123\", name: \"andrea\", surname: \"leo\" };\n                        if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n                        var signOptions = {\n                            subject: 'userexternal',\n                            audience: 'https://tiledesk.com/projects/' + savedProject._id,\n                        };\n\n                        var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n                        if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                        chai.request(server)\n                            .get('/testauth/noentitycheck')\n                            .set('Authorization', 'JWT ' + jwtToken)\n                            .send()\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n\n                                done();\n                            });\n                    });\n            });\n        });\n    });                           \n\n\n// // mocha test/authenticationJwt.js  --grep 'signinJWt-Project-external-user-YESAudYesSubjectAndRole'\n\n//     it('signinJWt-Project-external-user-YESAudYesSubjectAndRole', (done) => {\n\n\n//         //   this.timeout();\n    \n//             var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//             var pwd = \"pwd\";\n    \n//             userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n//                 // create(name, createdBy, settings)\n//                 projectService.create(\"test-signinJWt-user\", savedUser._id).then(function(savedProject) {                                              \n                                                \n//                     chai.request(server)\n//                     .post('/'+ savedProject._id + '/keys/generate')\n//                     .auth(email, pwd)\n//                     .send()\n//                     .end((err, res) => {\n//                         //console.log(\"res\",  res);\n//                         console.log(\"res.body\",  res.body);\n//                         res.should.have.status(200);\n//                         res.body.should.be.a('object');\n//                         expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                    \n\n//                         var externalUserObj = {_id:\"123\",name:\"andrea\", surname:\"leo\",role : 'agent'};\n                        \n//                         console.log(\"externalUserObj\", externalUserObj);\n\n\n//                         var signOptions = {                                                            \n//                             subject:  'userexternal',                                                                                     \n//                             audience:  'https://tiledesk.com/projects/'+savedProject._id ,                                              \n//                             };\n\n\n//                         var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret,signOptions);\n\n                            \n                                \n//                                 if (log) { console.log(\"jwtToken\", jwtToken); }\n                                \n//                                 chai.request(server)\n//                                 .get('/testauth/noentitycheck')\n//                                 .set('Authorization', 'JWT '+jwtToken)\n//                                 .send()\n//                                 .end((err, res) => {\n//                                     console.log(\"res.body\", res.body);\n//                                     res.should.have.status(200);\n                                    \n//                                     done();\n//                                 });\n\n                            \n                \n                        \n//                     });\n\n\n\n\n\n//                     });\n                            \n                            \n//                     });\n//                     });                           \n\n    it('signinJWt-bot-YESAudYesSubject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            // create(name, createdBy, settings)\n            projectService.create(\"test-signinJWt-user\", savedUser._id).then(function (savedProject) {\n                // create(name, url, projectid, user_id, type, description, webhook_url, webhook_enabled, language) {                                         \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\"}).then(function (savedBot) {\n\n                    var savedBotObj = savedBot.toObject();\n                    if (log) { console.log(\"savedBotObj\", savedBotObj); }\n\n                    var signOptions = {\n                        subject: 'bot',\n                        audience: 'https://tiledesk.com/bots/' + savedBot._id,\n                    };\n\n                    var jwtToken = jwt.sign(savedBotObj, savedBot.secret, signOptions);\n                    if (log) { console.log(\"jwtToken\", jwtToken); }\n\n                    chai.request(server)\n                        // .get('/'+ savedProject._id + '/faq_kb')\n                        .get('/' + savedProject._id + '/authtestWithRoleCheck/bot')\n\n                        .set('Authorization', 'JWT ' + jwtToken)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n\n                            done();\n                        });\n                });\n            });\n        });\n    }); \n\n});\n\n\n"
  },
  {
    "path": "test/authorization.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\n//var User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('Authorization', () => {\n\n    \n    it('userBelongsToProject', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"Pwd1234!\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.createAndReturnProjectAndProjectUser(\"test-auth\", savedUser._id).then(function (savedProjectAndPU) {\n                var savedProject = savedProjectAndPU.project;\n                leadService.createIfNotExists(\"request_id1-userBelongsToProject\", \"email@userBelongsToProject.com\", savedProject._id).then(function (createdLead) {\n                    // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {\n                    var id_request = \"test-userBelongsToProject\" + Date.now();\n                    requestService.createWithIdAndRequester(id_request, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n                        // var webhookContent =     { \"assignee\": 'assignee-member'}\n                        chai.request(server)\n                            .get('/' + savedProject._id + '/requests/' + savedRequest.request_id)\n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                done();\n                            });\n                    });\n                });\n            });\n        });\n    }).timeout(20000);\n\n\n    it('userNOTBelongsToProject', (done) => {\n\n        var email = \"test-signup-other-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            userService.signup(\"test-signup\" + Date.now() + \"@email.com\", pwd, \"Test Firstname other\", \"Test lastname other\").then(function (savedUserOther) {\n                projectService.createAndReturnProjectAndProjectUser(\"test-auth\", savedUserOther._id).then(function (savedProjectAndPU) {\n                    var savedProject = savedProjectAndPU.project;\n                    leadService.createIfNotExists(\"request_id1-userNOTBelongsToProject\", \"email@userNOTBelongsToProject.com\", savedProject._id).then(function (createdLead) {\n\n                        var id_request = \"test-auth\" + Date.now();\n                        requestService.createWithIdAndRequester(id_request, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n                            // var webhookContent =     { \"assignee\": 'assignee-member'}\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/requests/' + savedRequest._id)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n                                    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(403);\n\n                                    done();\n                                });\n                        });\n                    });\n                });\n            });\n        });\n    }).timeout(20000);\n\n\n        // it('userCANTAddProjectUser', (done) => {\n\n        \n        //     //   this.timeout();\n        \n        //        var email = \"test-signup-other-\" + Date.now() + \"@email.com\";\n        //        var pwd = \"pwd\";\n    \n        //        userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n        //         userService.signup(  \"test-signup\" + Date.now() + \"@email.com\" ,pwd, \"Test Firstname other\", \"Test lastname other\").then(function(savedUserOther) {\n        //             projectService.create(\"test-auth\", savedUserOther._id).then(function(savedProject) {\n        //                 requestService.createWithId(\"test-auth\", \"requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n        \n        //                     // var webhookContent =     { \"assignee\": 'assignee-member'}\n                                \n                    \n        //                     chai.request(server)\n        //                         .get('/'+ savedProject._id + '/requests/' + savedRequest._id )\n        //                         .auth(email, pwd)\n        //                         .end((err, res) => {\n        //                             //console.log(\"res\",  res);\n        //                             console.log(\"res.body\",  res.body);\n        //                             res.should.have.status(403);\n                                    \n        //                             done();\n        //                         });\n        \n                                \n        //                 });\n        //                 });\n        //                 });\n        //             });\n        //     }).timeout(20000);\n\n});\n\n\n"
  },
  {
    "path": "test/campaignsRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar Group = require('../models/group');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\n// chai.config.includeStack = true;\n\nlet log = false;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('CampaignsRoute', () => {\n\n\n  // mocha test/campaignsRoute.js  --grep 'directSimpleNoOut'\n  it('directSimpleNoOut', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"directSimple\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        var recipient = \"5f8972c82db41c003473cb03\";\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/campaigns/direct')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"ciao\", \"recipient\": recipient })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            // expect(res.body.success).to.equal(true); //CHECK IT! Why was it checking success and not queued?\n            expect(res.body.queued).to.equal(true);\n\n            done();\n          });\n      });\n    });\n  });\n\n  \n  // mocha test/campaignsRoute.js  --grep 'directSimple'\n  it('directSimple', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"directSimple\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        var recipient = \"5f8972c82db41c003473cb03\";\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/campaigns/direct')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"ciao\", \"recipient\": recipient, returnobject: true })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n\n            // WARNING! The service returns { queued: true } and not the message\n            // So the following expects can't work\n            // expect(res.body.channel_type).to.equal(\"direct\");\n            // expect(res.body.senderFullname).to.equal(\"Test Firstname Test lastname\");\n            // expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.recipient).to.equal(recipient);\n\n            expect(res.body.queued).to.equal(true);\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/campaignsRoute.js  --grep 'directGroupIdNoOut'\n  it('directGroupIdNoOut', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"directSimple\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        var userid = savedUser._id;\n\n        var email2 = \"test-message-create-\" + Date.now() + \"@email.com\";\n        userService.signup(email2, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser2) {\n\n          var newGroup = new Group({\n            name: \"group1\",\n            // members: [\"userid1\", \"userid2\"],\n            members: [userid, savedUser2._id.toString()],\n            trashed: false,\n            id_project: savedProject._id,\n            createdBy: userid,\n            updatedBy: userid\n          });\n          newGroup.save(function (err, savedGroup) {\n            \n            if (log) { console.log(\"savedGroup\", savedGroup); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/campaigns/direct')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send({ \"text\": \"ciao\", \"group_id\": savedGroup._id.toString() })\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                \n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                // expect(res.body.success).to.equal(true); //CHECK IT! Why was it checking success and not queued?\n                expect(res.body.queued).to.equal(true);\n\n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/campaignsRoute.js  --grep 'directGroupId2'\n  it('directGroupId2', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"directSimple\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        var userid = savedUser._id;\n\n        var email2 = \"test-message-create-\" + Date.now() + \"@email.com\";\n        userService.signup(email2, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser2) {\n\n          var newGroup = new Group({\n            name: \"group1\",\n            // members: [\"userid1\", \"userid2\"],\n            members: [userid, savedUser2._id.toString()],\n            trashed: false,\n            id_project: savedProject._id,\n            createdBy: userid,\n            updatedBy: userid\n          });\n          newGroup.save(function (err, savedGroup) {\n            \n            if (log) { console.log(\"savedGroup\", savedGroup); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/campaigns/direct')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send({ \"text\": \"ciao\", \"group_id\": savedGroup._id.toString(), returnobject: true })\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n\n                res.should.have.status(200);\n\n                // WARNING! The service returns { queued: true } and not the message\n                // res.body.should.be.a('array');\n                // expect(res.body.length).to.equal(2);\n                // expect(res.body[0].recipient).to.equal(userid.toString());\n                // expect(res.body[1].recipient).to.equal(savedUser2._id.toString());\n                \n                expect(res.body.queued).to.equal(true);\n\n\n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n});\n\n\n"
  },
  {
    "path": "test/cannedRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet expect = require('chai').expect;\n\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nconst projectService = require('../services/projectService');\nconst userService = require('../services/userService');\nvar RoleConstants = require(\"../models/roleConstants\");\nconst Project_user = require('../models/project_user');\nlet should = chai.should();\n\nlet log = false;\n\nchai.use(chaiHttp);\n\ndescribe('CannedRoute', () => {\n\n    it('new canned by owner/admin', (done) => {\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then(savedUser => {\n            projectService.create(\"test1\", savedUser._id).then(savedProject => {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/canned/')\n                    .auth(email, pwd)\n                    .set('content-type', 'application/json')\n                    .send({ \"title\": \"Test Title\", \"text\": \"Test Text\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body: \", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        res.body.should.have.property('title').eql(\"Test Title\");\n                        res.body.should.have.property('text').eql(\"Test Text\");\n                        res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n                        res.body.should.have.property('shared').eql(true);\n\n                        done();\n                    })\n            })\n        })\n    })\n\n    it('new canned by agent', (done) => {\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then(savedUser => {\n            projectService.create(\"test1\", savedUser._id).then(savedProject => {\n\n                Project_user.findOneAndUpdate({id_project: savedProject._id, id_user: savedUser._id }, { role: RoleConstants.AGENT }, function(err, savedProject_user){\n                    chai.request(server)\n                    .post('/' + savedProject._id + '/canned/')\n                    .auth(email, pwd)\n                    .set('content-type', 'application/json')\n                    .send({ title: \"Test Title\", text: \"Test Text\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body: \", res.body); }\n\n                        res.body.should.be.a('object');\n                        res.body.should.have.property('title').eql(\"Test Title\");\n                        res.body.should.have.property('text').eql(\"Test Text\");\n                        res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n                        res.body.should.have.property('shared').eql(false);\n\n                        done();\n                    })  \n                })\n            })\n        })\n    })\n\n    it('get canned', (done) => {\n\n        var email_owner = \"owner-signup-\" + Date.now() + \"@email.com\";\n        var email_agent = \"agent-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email_owner, pwd, \"Owner Firstname\", \"Owner Lastname\").then(savedOwner => {\n            userService.signup(email_agent, pwd, \"Agent Firstname\", \"Agent Lastname\").then(savedAgent => {\n                projectService.create(\"test1\", savedOwner._id).then(savedProject => {\n\n                    // invite Agent on savedProject (?)\n                    chai.request(server)\n                        .post('/' + savedProject._id + \"/project_users/invite\")\n                        .auth(email_owner, pwd)\n                        .set('content-type', 'application/json')\n                        .send({ email: email_agent, role: \"agent\", userAvailable: false })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body); }\n\n                            res.should.have.status(200);\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/canned/\")\n                                .auth(email_owner, pwd)\n                                .set('content-type', 'application/json')\n                                .send({ title: \"Test1 Title\", text: \"Test1 Text\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + \"/canned/\")\n                                        .auth(email_agent, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({ title: \"Test2 Title\", text: \"Test2 Text\" })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + \"/canned/\")\n                                                .auth(email_owner, pwd)\n                                                .set('content-type', 'application-json')\n                                                .send()\n                                                .end((err, res) => {\n\n                                                    res.should.have.status(200);\n                                                    //res.body.should.be.a('array');\n\n                                                    expect(res.body).to.be.an('array')\n                                                    expect(res.body.length).to.equal(1);\n\n                                                    chai.request(server)\n                                                        .get('/' + savedProject._id + \"/canned/\")\n                                                        .auth(email_agent, pwd)\n                                                        .set('content-type', 'application-json')\n                                                        .send()\n                                                        .end((err, res) => {\n\n                                                            res.should.have.status(200);\n                                                            //res.body.should.be.a('array');\n\n                                                            expect(res.body).to.be.an('array')\n                                                            expect(res.body.length).to.equal(2);\n\n                                                            done();\n\n                                                        })\n                                                })\n                                        })\n                                })\n                        })\n                })\n            })\n        })\n    }).timeout(5000);\n\n})"
  },
  {
    "path": "test/dateUtils.test.js",
    "content": "const { expect } = require('chai');\nconst datesUtil = require('../utils/datesUtil');\nconst moment = require('moment-timezone');\n\ndescribe('datesUtil', () => {\n\n    it('test1', () => {\n        const startDate = '21/01/2026';\n        const endDate = '21/01/2026';\n        const timezone = 'Europe/Rome';\n\n        const res = datesUtil.createDateRangeQuery(startDate, endDate, timezone);\n\n        expect(res).to.have.property('createdAt');\n        expect(res.createdAt).to.have.property('$gte');\n        expect(res.createdAt).to.have.property('$lt');\n\n        let startDateUTC = new Date(\"2026-01-20T23:00:00.000Z\").toISOString();\n        let endDateUTC = new Date(\"2026-01-21T23:00:00.000Z\").toISOString();\n    \n        expect(res.createdAt.$gte.toISOString()).to.equal(startDateUTC);\n        expect(res.createdAt.$lt.toISOString()).to.equal(endDateUTC);\n    })\n\n    it('test2', () => {\n        const startDate = '20/01/2026, 12:25';\n        const endDate = '20/01/2026, 15:25 ';\n        const timezone = 'Europe/Rome';\n\n        const res = datesUtil.createDateRangeQuery(startDate, endDate, timezone);\n\n        console.log(res);\n\n        expect(res).to.have.property('createdAt');\n        expect(res.createdAt).to.have.property('$gte');\n        expect(res.createdAt).to.have.property('$lte');\n\n        let startDateUTC = new Date(\"2026-01-20T11:25:00.000Z\").toISOString();\n        let endDateUTC = new Date(\"2026-01-20T14:25:00.000Z\").toISOString();\n    \n        expect(res.createdAt.$gte.toISOString()).to.equal(startDateUTC);\n        expect(res.createdAt.$lte.toISOString()).to.equal(endDateUTC);\n    })\n//   describe('parseDate', () => {\n//     it('should parse DD/MM/YYYY format and apply timezone', () => {\n//       const dateStr = '28/02/2023';\n//       const timezone = 'Europe/Rome';\n//       const m = datesUtil.parseDate(dateStr, timezone);\n//       expect(m).to.be.an.instanceof(moment);\n//       expect(m.tz()).to.equal(timezone);\n//       expect(m.format('DD/MM/YYYY')).to.equal('28/02/2023');\n//     });\n\n//     it('should parse DD/MM/YYYY HH:mm:ss format and apply timezone', () => {\n//       const dateStr = '01/03/2023 13:14:15';\n//       const timezone = 'America/New_York';\n//       const m = datesUtil.parseDate(dateStr, timezone);\n\n//       expect(m.format('DD/MM/YYYY HH:mm:ss')).to.equal('01/03/2023 07:14:15');\n//       expect(m.tz()).to.equal(timezone);\n//     });\n\n//     it('should throw error for invalid timezone', () => {\n//       expect(() => datesUtil.parseDate('01/03/2023', 'Invalid/Zone')).to.throw('Invalid timezone');\n//     });\n\n//     it('should throw error for unparseable date', () => {\n//       expect(() => datesUtil.parseDate('notadate', 'Europe/Rome')).to.throw('Unable to parse the date');\n//     });\n//   });\n\n\n//   describe('hasTimeComponent', () => {\n//     it('should return true for string with time', () => {\n//       expect(datesUtil.hasTimeComponent('20/02/2021 23:31:01')).to.be.true;\n//       expect(datesUtil.hasTimeComponent('20/02/2021 02:31')).to.be.true;\n//     });\n//     it('should return false for string without time', () => {\n//       expect(datesUtil.hasTimeComponent('20/02/2021')).to.be.false;\n//       expect(datesUtil.hasTimeComponent('01/03/2022')).to.be.false;\n//     });\n//   });\n\n//   describe('convertLocalDatesToUTC', () => {\n//     it('should convert start and end dates and output correct UTC ISO strings', () => {\n//       const timezone = 'Europe/Rome';\n//       const startDate = '01/06/2021';\n//       const endDate = '05/06/2021';\n//       const res = datesUtil.convertLocalDatesToUTC(startDate, endDate, timezone);\n\n//       // startDateUTC should be 2021-06-01T00:00:00.000Z in UTC\n//       expect(res.startDateUTC).to.match(/^2021-05-31T22:00:00/);\n//       // endDateUTC should be 2021-06-06T00:00:00.000Z in UTC (start of *next* day)\n//       expect(res.endDateUTC).to.match(/^2021-06-05T22:00:00/);\n//     });\n\n//     it('should convert when time is included in the dates', () => {\n//       const timezone = 'America/New_York';\n//       const startDate = '20/07/2022 09:30:00';\n//       const endDate = '21/07/2022 17:45:10';\n//       const res = datesUtil.convertLocalDatesToUTC(startDate, endDate, timezone);\n//       // The result.startDateUTC and endDateUTC should be the same as the input local time mapped to UTC\n//       expect(new Date(res.startDateUTC).toISOString()).to.equal(res.startDateUTC);\n//       expect(new Date(res.endDateUTC).toISOString()).to.equal(res.endDateUTC);\n//     });\n\n//     it('should throw if both startDate and endDate are missing', () => {\n//       expect(() => datesUtil.convertLocalDatesToUTC(null, null, 'Europe/Rome')).to.throw('At least one of startDate or endDate must be provided');\n//     });\n//   });\n\n//   describe('createDateRangeQuery', () => {\n//     it('should generate MongoDB query with $gte and $lt for date only', () => {\n//       const tz = 'Europe/Rome';\n//       const res = datesUtil.createDateRangeQuery('02/02/2022', '05/02/2022', tz, 'createdAt');\n//       expect(res).to.have.property('createdAt');\n//       expect(res.createdAt).to.have.property('$gte');\n//       expect(res.createdAt).to.have.property('$lt');\n//       expect(res.createdAt).to.not.have.property('$lte');\n//     });\n\n//     it('should use $lte operator for endDate with time component', () => {\n//       const tz = 'Europe/Rome';\n//       const r = datesUtil.createDateRangeQuery('02/02/2022 09:00:00', '05/02/2022 18:23:10', tz, 'createdAt');\n//       expect(r.createdAt).to.have.property('$gte');\n//       expect(r.createdAt).to.have.property('$lte');\n//       expect(r.createdAt).to.not.have.property('$lt');\n//     });\n\n//     it('should allow custom fieldName', () => {\n//       const tz = 'Europe/Rome';\n//       const result = datesUtil.createDateRangeQuery('10/08/2023', '15/08/2023', tz, 'customField');\n//       expect(result).to.have.property('customField');\n//       expect(result.customField).to.have.property('$gte');\n//       expect(result.customField).to.have.property('$lt');\n//     });\n\n//     it('should allow only startDate', () => {\n//       const tz = 'Europe/Rome';\n//       const result = datesUtil.createDateRangeQuery('10/09/2023', undefined, tz);\n//       expect(result).to.have.property('createdAt');\n//       expect(result.createdAt).to.have.property('$gte');\n//       expect(result.createdAt).to.not.have.property('$lt');\n//       expect(result.createdAt).to.not.have.property('$lte');\n//     });\n\n//     it('should allow only endDate', () => {\n//       const tz = 'Europe/Rome';\n//       const result = datesUtil.createDateRangeQuery(null, '10/09/2023', tz);\n//       expect(result).to.have.property('createdAt');\n//       expect(result.createdAt).to.have.property('$lt');\n//       expect(result.createdAt).to.not.have.property('$gte');\n//       expect(result.createdAt).to.not.have.property('$lte');\n//     });\n\n//     it('should throw error for invalid timezone', () => {\n//       expect(() => datesUtil.createDateRangeQuery('01/01/2023', '02/01/2023', 'Invalid/Timezone')).to.throw('Invalid timezone');\n//     });\n//   });\n\n});\n"
  },
  {
    "path": "test/departmentService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\nvar roleConstants = require('../models/roleConstants');\nvar Project_user = require(\"../models/project_user\");\nvar userService = require('../services/userService');\nvar departmentService = require('../services/departmentService');\nvar requestService = require('../services/requestService');\nvar routingConstants = require('../models/routingConstants');\nvar leadService = require('../services/leadService');\n\nmongoose.connect(config.databasetest);\nrequire('../services/mongoose-cache-fn')(mongoose);\n\nvar projectService = require('../services/projectService');\n\n\nlet log = false;\n\n// var appRules = require('../rules/appRules');\n// appRules.start();\n\n\ndescribe('DepartmentService()', function () {\n\n  it('createFirstWithAssignedDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-department-create\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      userService.signup(email + '2', pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n        userService.signup(email + '3', pwd, \"Test Firstname3\", \"Test lastname3\").then(function (savedUser3) {\n          projectService.create(\"createWithAssignedDepartment\", savedUser._id).then(function (savedProject) {\n\n\n\n            var pu1 = new Project_user({\n              // _id: new mongoose.Types.ObjectId(),\n              id_project: savedProject._id,\n              id_user: savedUser2._id,\n              role: roleConstants.AGENT,\n              roleType : RoleConstants.TYPE_AGENTS,   \n              user_available: true,\n              createdBy: savedUser2._id,\n              updatedBy: savedUser2._id,\n            });\n            pu1.save(function (err, savedProject_user2) {\n              winston.debug(\"err\", err);\n              winston.debug(\"savedProject_user2\", savedProject_user2.toObject());\n\n\n              var pu2 = new Project_user({\n                // _id: new mongoose.Types.ObjectId(),\n                id_project: savedProject._id,\n                id_user: savedUser3._id,\n                role: roleConstants.AGENT,\n                roleType : RoleConstants.TYPE_AGENTS,                   \n                user_available: true,\n                createdBy: savedUser3._id,\n                updatedBy: savedUser3._id,\n              });\n\n              pu2.save(function (err, savedProject_user3) {\n                winston.debug(\"savedProject_user3\", savedProject_user3.toObject());\n\n                departmentService.create(\"PooledDepartment-for-createWithIdWith-createFirstWithAssignedDepartment\", savedProject._id, routingConstants.ASSIGNED, savedUser._id).then(function (createdDepartment) {\n\n                  expect(createdDepartment.hasBot).to.equal(false);\n\n\n                  // requestService.createWithId(\"request_id1\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest) {\n\n                  // getOperators(departmentid, projectid, nobot) {\n                  departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult0) {\n                    winston.debug(\"resolve operatorsResult0\", operatorsResult0); //time invariant?\n                    departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult) {\n                      winston.info(\"resolve operatorsResult\", operatorsResult);\n\n                      expect(operatorsResult.department._id.toString()).to.equal(createdDepartment._id.toString());\n                      expect(operatorsResult.available_agents.length).to.equal(3);\n\n                      //time invariant?\n                      expect(operatorsResult0.available_agents[0]._id.toString()).to.equal(operatorsResult.available_agents[0]._id.toString());\n                      expect(operatorsResult0.available_agents[1]._id.toString()).to.equal(operatorsResult.available_agents[1]._id.toString());\n                      expect(operatorsResult0.available_agents[2]._id.toString()).to.equal(operatorsResult.available_agents[2]._id.toString());\n\n                      expect(operatorsResult.operators.length).to.equal(1);\n                      expect(operatorsResult.agents.length).to.equal(3);\n                      expect(operatorsResult.group).to.equal(undefined);\n\n                      done();\n                    });\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n\n  it('createRoundRobinWithAssignedDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-department-create\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      userService.signup(email + '2', pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n        userService.signup(email + '3', pwd, \"Test Firstname3\", \"Test lastname3\").then(function (savedUser3) {\n          // projectService.create(\"createWithAssignedDepartment\", savedUser._id).then(function(savedProject) {\n          projectService.createAndReturnProjectAndProjectUser(\"createWithAssignedDepartment\", savedUser._id).then(function (savedProjectAndPU) {\n            var savedProject = savedProjectAndPU.project;\n\n            var pu1 = new Project_user({\n              // _id: new mongoose.Types.ObjectId(),\n              id_project: savedProject._id,\n              id_user: savedUser2._id,\n              role: roleConstants.AGENT,\n              roleType : RoleConstants.TYPE_AGENTS,   \n              user_available: true,\n              createdBy: savedUser2._id,\n              updatedBy: savedUser2._id,\n            });\n            pu1.save(function (err, savedProject_user2) {\n              winston.debug(\"err\", err);\n              winston.debug(\"savedProject_user2\", savedProject_user2.toObject());\n\n\n              // var pu2 =  new Project_user({\n              //   // _id: new mongoose.Types.ObjectId(),\n              //   id_project: savedProject._id,\n              //   id_user: savedUser3._id,\n              //   role: roleConstants.AGENT,\n              //   user_available: true,\n              //   createdBy: savedUser3._id,\n              //   updatedBy: savedUser3._id,\n              // });\n\n              // pu2.save(function (err, savedProject_user3) {\n              // winston.debug(\"savedProject_user3\", savedProject_user3.toObject());\n\n              departmentService.create(\"PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedDepartment\", savedProject._id, routingConstants.ASSIGNED, savedUser._id).then(function (createdDepartment) {\n\n                leadService.createIfNotExists(\"request_id1-PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedDepartment\", \"email@getallWithLoLead.com\", savedProject._id).then(function (createdLead) {\n\n                  // winston.info(\"createdLead\", createdLead.toObject());\n                  // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) {\n                  var now = Date.now();\n\n                  requestService.createWithIdAndRequester(\"request_id1-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest) {\n\n                    // requestService.createWithId(\"request_id1\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest) {\n\n                    // getOperators(departmentid, projectid, nobot) {\n                    departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult0) {\n                      winston.debug(\"resolve operatorsResult0\", operatorsResult0); //time invariant?\n\n                      // requestService.createWithId(\"request_id2\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest2) {\n                      requestService.createWithIdAndRequester(\"request_id2-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest2) {\n\n                        departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult) {\n                          winston.info(\"resolve operatorsResult\", operatorsResult);\n\n                          expect(operatorsResult.department._id.toString()).to.equal(createdDepartment._id.toString());\n                          expect(operatorsResult.available_agents.length).to.equal(2);\n\n                          //time invariant?\n                          expect(operatorsResult0.available_agents[0]._id.toString()).to.equal(operatorsResult.available_agents[0]._id.toString());\n                          expect(operatorsResult0.available_agents[1]._id.toString()).to.equal(operatorsResult.available_agents[1]._id.toString());\n                          // expect(operatorsResult0.available_agents[2]._id.toString()).to.equal(operatorsResult.available_agents[2]._id.toString());                          \n\n                          expect(operatorsResult.agents.length).to.equal(2);\n                          expect(operatorsResult.group).to.equal(undefined);\n\n                          expect(operatorsResult0.operators[0].id_user.toString()).to.not.equal(savedRequest.participants[0].toString());\n                          expect(operatorsResult.operators[0].id_user.toString()).to.not.equal(savedRequest2.participants[0].toString());\n                          // expect(operatorsResult.operators[0].id_user.toString()).to.equal(savedRequest.participants[0].toString());\n\n                          done();\n                        });\n                      });\n                    });\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n\n  }).timeout(20000);\n\n\n  // mocha test/departmentService.js  --grep 'createRoundRobinWithAssignedDepartmentNoBeforeRequestOneAgent'\n  it('createRoundRobinWithAssignedDepartmentNoBeforeRequestOneAgent', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-department-create\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      userService.signup(email + '2', pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n        userService.signup(email + '3', pwd, \"Test Firstname3\", \"Test lastname3\").then(function (savedUser3) {\n          projectService.create(\"createWithAssignedDepartment\", savedUser._id).then(function (savedProject) {\n\n            departmentService.create(\"PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedDepartmentNoBeforeRequestOneAgent\", savedProject._id, routingConstants.ASSIGNED, savedUser._id).then(function (createdDepartment) {\n\n              // getOperators(departmentid, projectid, nobot) {\n              departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult0) {\n                winston.debug(\"resolve operatorsResult0\", operatorsResult0); //time invariant?\n                expect(operatorsResult0.operators[0].id_user.toString()).to.equal(savedUser._id.toString());\n                done();\n              });\n            });\n          });\n        });\n      });\n    });\n  }).timeout(20000);\n\n\n  it('createRoundRobinWithAssignedALLOFFLINEDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-department-create\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      userService.signup(email + '2', pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n        // projectService.create(\"createWithAssignedDepartment\", savedUser._id).then(function(savedProject) {\n        projectService.createAndReturnProjectAndProjectUser(\"createWithId-createWithAssignedDepartment\", savedUser._id).then(function (savedProjectAndPU) {\n          var savedProject = savedProjectAndPU.project;\n          Project_user.findOneAndUpdate({ id_project: savedProject._id, id_user: savedUser._id, }, { user_available: false }, { new: true, upsert: false }, function (err, updatedProject_user) {\n            winston.debug(\"updatedProject_user\", updatedProject_user);\n\n            var pu1 = new Project_user({\n              id_project: savedProject._id,\n              id_user: savedUser2._id,\n              role: roleConstants.AGENT,\n              roleType : RoleConstants.TYPE_AGENTS,   \n              user_available: false,\n              createdBy: savedUser2._id,\n              updatedBy: savedUser2._id,\n            });\n\n            pu1.save(function (err, savedProject_user2) {\n              winston.debug(\"err\", err);\n              winston.debug(\"savedProject_user2\", savedProject_user2.toObject());\n\n\n              departmentService.create(\"PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedALLOFFLINEDepartment\", savedProject._id, routingConstants.ASSIGNED, savedUser._id).then(function (createdDepartment) {\n                // requestService.createWithIdAndRequester(\"request_id1-createRoundRobinWithAssignedALLOFFLINEDepartment\", savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest) {\n                // requestService.createWithId(\"request_id1\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest) {\n\n                leadService.createIfNotExists(\"request_id1-PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedALLOFFLINEDepartment\", \"email@getallWithLoLead.com\", savedProject._id).then(function (createdLead) {\n\n                  // winston.info(\"createdLead\", createdLead.toObject());\n                  // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) {\n                  var now = Date.now();\n                  requestService.createWithIdAndRequester(\"request_id1-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest) {\n\n                    // getOperators(departmentid, projectid, nobot) {\n                    departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult0) {\n                      winston.debug(\"resolve operatorsResult0\", operatorsResult0); //time invariant?\n\n                      // requestService.createWithId(\"request_id2\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest2) {\n                      requestService.createWithIdAndRequester(\"request_id2-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest2) {\n\n                        departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult) {\n                          winston.info(\"resolve operatorsResult\", operatorsResult);\n\n                          expect(operatorsResult.department._id.toString()).to.equal(createdDepartment._id.toString());\n                          expect(operatorsResult.available_agents.length).to.equal(0);\n\n                          expect(operatorsResult.agents.length).to.equal(2);\n                          expect(operatorsResult.group).to.equal(undefined);\n\n                          done();\n                        });\n                      });\n                    });\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n  }).timeout(20000);\n\n\n\n\n\n\n\n  //   (node:74274) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'id_user' of undefined\n  //   at /Users/andrealeo/dev/chat21/tiledesk-server/services/requestService.js:55:56\n  //   at processTicksAndRejections (internal/process/next_tick.js:81:5)\n  // (node:74274) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)\n\n  it('createRoundRobinWithAssignedLastOperatorNotAvailableAndOtherNotAvailableDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-department-create\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      userService.signup(email + '2', pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n        projectService.createAndReturnProjectAndProjectUser(\"createWithAssignedDepartment\", savedUser._id).then(function (savedProjectAndPU) {\n\n          var savedProject = savedProjectAndPU.project;\n\n          var pu1 = new Project_user({\n            id_project: savedProject._id,\n            id_user: savedUser2._id,\n            role: roleConstants.AGENT,\n            roleType : RoleConstants.TYPE_AGENTS,  \n            user_available: false,\n            createdBy: savedUser2._id,\n            updatedBy: savedUser2._id,\n          });\n\n          pu1.save(function (err, savedProject_user2) {\n            winston.debug(\"err\", err);\n            winston.debug(\"savedProject_user2\", savedProject_user2.toObject());\n\n            departmentService.create(\"PooledDepartment-for-createWithIdWith-createRoundRobinWithAssignedLastOperatorNotAvailableAndOtherNotAvailableDepartment\", savedProject._id, routingConstants.ASSIGNED, savedUser._id).then(function (createdDepartment) {\n              var now = Date.now();\n              leadService.createIfNotExists(\"request_id1-getallWithLoLead\", \"email@getallWithLoLead.com\", savedProject._id).then(function (createdLead) {\n                requestService.createWithIdAndRequester(\"request_id1-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest) {\n\n                  // requestService.createWithId(\"request_id1\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest) {\n\n                  // getOperators(departmentid, projectid, nobot) {\n                  departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult0) {\n                    winston.debug(\"resolve operatorsResult0\", operatorsResult0); //time invariant?\n\n                    Project_user.findOneAndUpdate({ id_project: savedProject._id, id_user: savedUser._id, }, { user_available: false }, { new: true, upsert: false }, function (err, updatedProject_user) {\n                      winston.debug(\"updatedProject_user\", updatedProject_user);\n\n                      // requestService.createWithId(\"request_id2\", \"requester_id1\", savedProject._id, \"first_text\", createdDepartment._id).then(function(savedRequest2) {\n                      requestService.createWithIdAndRequester(\"request_id2-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest2) {\n\n                        departmentService.getOperators(createdDepartment._id, savedProject._id, false).then(function (operatorsResult) {\n                          winston.info(\"resolve operatorsResult\", operatorsResult);\n\n                          expect(operatorsResult.department._id.toString()).to.equal(createdDepartment._id.toString());\n                          expect(operatorsResult.available_agents.length).to.equal(0);\n\n                          expect(operatorsResult.agents.length).to.equal(2);\n                          expect(operatorsResult.group).to.equal(undefined);\n\n                          done();\n                        });\n                      });\n                    });\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n  }).timeout(20000);\n\n});\n"
  },
  {
    "path": "test/emailService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nmongoose.connect(config.databasetest);\n\nvar projectService = require(\"../services/projectService\");\nconst emailService = require('../services/emailService');\n\nlet log = false;\n\ndescribe('EmailService', function () {\n\n\n  it('direct', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n\n      let request_id = \"support-group-\" + savedProject._id + \"-123456\";\n\n      //let text = \"this is\\n<b>the</b>\\ntext\";\n      //let text = 'this is <script>alert(\"XSS\")</script>';\n      let text = 'Go to [Google](https://google.com)';\n      emailService.sendEmailDirect(\"my@email.com\", text, savedProject._id, request_id, \"Suubject\").then((response) => {\n        console.log(\"response: \", response);\n        done();\n      }).catch((err) => {\n        console.error(\"err: \", err)\n        done();\n      })\n\n      \n    });\n  });\n\n});"
  },
  {
    "path": "test/faqRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nconst Project_user = require('../models/project_user');\n\nconst example_data = require('./fixtures/example-json-multiple-operation-mock');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\nconst uuidv4 = require('uuid/v4');\nconst project_user = require('../models/project_user');\nconst roleConstants = require('../models/roleConstants');\n\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nlet log = false;\n\nchai.use(chaiHttp);\n\ndescribe('FaqKBRoute', () => {\n\n    describe('Create', () => {\n\n        it('create', (done) => {\n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create bot res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.public).to.exist;\n                            expect(res.body.public).to.equal(false);\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", attributes: { attr1: { one: \"one\", two: \"two\"}, attr2: {three: \"three\"}}, intent_id: 'custom-intent-id' }) // if intent_id is null the value will be the default one\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create intent res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.question).to.equal(\"question1\");\n                                    expect(res.body.answer).to.equal(\"answer1\");\n                                    expect(res.body.intent_display_name).to.not.equal(undefined);\n                                    expect(res.body.webhook_enabled).to.equal(false);\n    \n                                    done();\n                                });\n    \n                        });\n    \n    \n                });\n            });\n    \n        });\n    \n        it('create with form (createFaqKb function)', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n            let example_form = {\n                fields: [\n                    {\n                        name: \"userFullname\",\n                        type: \"text\",\n                        label: \"What is your name?\"\n                    },\n                    {\n                        name: \"userEmail\",\n                        type: \"text\",\n                        regex: \"/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'+/0-9=?A-Z^_`a-z{|}~]+(.[-!#$%&'+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)+$/\",\n                        label: \"Your email?\",\n                        errorLabel: \"This email address is invalid\\n\\nCan you insert a correct email address?\"\n                    }\n                ]\n            }\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"external\", language: 'fr' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, form: example_form })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.form).to.exist;\n                                    res.body.form.should.be.a('object');\n                                    expect(res.body.intent_display_name).to.not.equal(undefined);\n                                    expect(res.body.webhook_enabled).to.equal(false);\n    \n                                    done();\n                                });\n                        });\n    \n    \n                });\n            });\n    \n        }).timeout(20000);\n    \n        it('createWithLanguage', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: \"it\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"it\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\" })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.question).to.equal(\"question1\");\n                                    expect(res.body.answer).to.equal(\"answer1\");\n                                    expect(res.body.intent_display_name).to.not.equal(undefined);\n                                    expect(res.body.webhook_enabled).to.equal(false);\n                                    expect(res.body.language).to.equal(\"it\");\n                                    done();\n                                });\n    \n                        });\n    \n    \n                });\n            });\n    \n        });\n    \n        it('createWithIntentDisplayNameAndWebhookEnalbed', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n                            \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", webhook_enabled: true, intent_display_name: \"intent_display_name1\" })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.question).to.equal(\"question1\");\n                                    expect(res.body.answer).to.equal(\"answer1\");\n                                    expect(res.body.intent_display_name).to.equal(\"intent_display_name1\");\n                                    expect(res.body.webhook_enabled).to.equal(true);\n    \n                                    done();\n                                });\n    \n                        });\n    \n    \n                });\n            });\n    \n        });\n\n    })\n\n    describe('Get', () => {\n\n        it('Get faqs with role agent', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"tilebot\", language: 'en', template: 'blank' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let id_faq_kb = res.body._id;\n\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('array');\n                                    expect(res.body.length).to.equal(3);\n\n                                    let intent = res.body.find(f => f.intent_display_name === 'welcome');\n                                    expect(intent.agents_available).to.equal(false);\n                                    \n                                    let intent_id = intent._id;\n                                    \n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/faq/' + intent_id)\n                                        .auth(email, pwd)\n                                        .send({ id_faq_kb: id_faq_kb, agents_available: true })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.agents_available).to.equal(true);\n\n                                            Project_user.findOneAndUpdate({ id_project: savedProject._id, id_user: savedUser._id }, { role: roleConstants.AGENT }, (err, savedProject_user) => {\n\n                                                chai.request(server)\n                                                    .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                                    .auth(email, pwd)\n                                                    .end((err, res) => {\n                                                    \n                                                        if (err) { console.error(\"err: \", err); }\n                                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                                        res.should.have.status(200);\n                                                        res.body.should.be.a('array');\n                                                        expect(res.body.length).to.equal(1);\n                                                        expect(res.body[0].intent_display_name).to.equal('welcome');\n                                                        expect(res.body[0].agents_available).to.equal(true);\n\n                                                        done();\n\n                                                    })\n                                            })\n                                        })\n\n                                })\n\n                            \n\n                        });\n\n\n                });\n            });\n\n        }).timeout(20000);\n\n\n        it('searchFaqs', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n    \n                            chai.request(server)\n                                //.get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb + \"&page=0&limit=25&text=looking\")\n                                .auth(email, pwd)\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"found these faqs: \\n\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array');\n    \n    \n                                    done();\n    \n                                })\n                        })\n                })\n            })\n        });\n    \n    })\n\n    describe('Update', () => {\n\n        it('update', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", attributes: { attr1: {one: \"one\", two: \"two\"}} })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.question).to.equal(\"question1\");\n                                    expect(res.body.answer).to.equal(\"answer1\");\n                                    expect(res.body.intent_display_name).to.not.equal(undefined);\n                                    expect(res.body.webhook_enabled).to.equal(false);\n    \n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/faq/' + res.body._id)\n                                        .auth(email, pwd)\n                                        .send({ id_faq_kb: id_faq_kb, question: \"question2\", answer: \"answer2\", webhook_enabled: true, attributes: { two: \"twooo\" } })\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n    \n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                            expect(res.body.question).to.equal(\"question2\");\n                                            expect(res.body.answer).to.equal(\"answer2\");\n                                            expect(res.body.intent_display_name).to.not.equal(undefined);\n                                            expect(res.body.webhook_enabled).to.equal(true);\n    \n                                            done();\n                                        });\n    \n                                });\n    \n                        });\n    \n    \n                });\n            });\n    \n        });\n    \n        it('update attributes', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\" })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_faq_kb).to.equal(id_faq_kb);\n                                    expect(res.body.question).to.equal(\"question1\");\n                                    expect(res.body.answer).to.equal(\"answer1\");\n                                    expect(res.body.intent_display_name).to.not.equal(undefined);\n                                    expect(res.body.webhook_enabled).to.equal(false);\n    \n                                    chai.request(server)\n                                        .patch('/' + savedProject._id + '/faq/' + res.body._id + '/attributes')\n                                        .auth(email, pwd)\n                                        .send({\n                                            \"first_parameter\": {\n                                                \"x\": \"first\",\n                                                \"y\": \"second\"\n                                            },\n                                            \"color\": {\n                                                \"first\": \"first\",\n                                            }\n                                        })\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) {\n                                                console.log(\"res.body attributes\", res.body);\n                                                console.log(\"res.body attributes\", res.body.attributes);\n                                            }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.attributes).to.not.equal(undefined);\n    \n                                            done();\n                                        });\n    \n                                });\n    \n                        });\n    \n    \n                });\n            });\n        })\n    \n        it('updateBulkOperations', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-updatebulkops\", savedUser._id).then((savedProject) => {\n    \n                    //console.log(\"EXAMPLE DATA: \", JSON.stringify(example_data));\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq/ops_update')\n                        .auth(email, pwd)\n                        .send(example_data.json_multiple_operation) // set up the payload\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n    \n                            done();\n                        })\n    \n                })\n            })\n        })\n\n    })\n\n\n    describe('Upload', () => {\n\n        it('uploadcsv', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-uploadcsv\", savedUser._id).then(function (savedProject) {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq/uploadcsv')\n                                .auth(email, pwd)\n                                .set('Content-Type', 'text/csv')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-faqs.csv')), 'example-faqs.csv')\n                                .field('id_faq_kb', id_faq_kb)\n                                .field('delimiter', ';')\n                                // .send({id_faq_kb: id_faq_kb})       \n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n    \n                                    done();\n                                });\n    \n                        });\n    \n                });\n            });\n    \n        });\n    \n        it('uploadcsvWithLanguage', (done) => {\n    \n    \n            //   this.timeout();\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-uploadcsv\", savedUser._id).then(function (savedProject) {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", language: \"it\" })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"it\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq/uploadcsv')\n                                .auth(email, pwd)\n                                .set('Content-Type', 'text/csv')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-faqs.csv')), 'example-faqs.csv')\n                                .field('id_faq_kb', id_faq_kb)\n                                .field('delimiter', ';')\n                                // .send({id_faq_kb: id_faq_kb})       \n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n    \n                                    done();\n                                });\n    \n                        });\n    \n                });\n            });\n    \n        });\n\n    })\n\n    describe('IntentsEngine', () => {\n\n        it('intentsEngine on', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"blank\", intentsEngine: 'tiledesk-ai' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\\nciao\\nbuongiorno\", answer: \"answer1\" })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) {  console.log(\"intentEngin on resbody (create faq): \\n\", res.body); }\n    \n                                    res.should.have.status(200);\n    \n                                    done();\n    \n                                })\n                        })\n                })\n            })\n        });\n\n    })\n\n    describe('Delete', () => {\n\n        it('delete with _id', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", intentsEngine: 'tiledesk-ai' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create chatbot res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\" })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create intent res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    let faqid = res.body._id;\n    \n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/faq/' + faqid)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"delete intent res.body\", res.body); }\n    \n                                            res.should.have.status(200);\n    \n                                            done();\n                                        })\n    \n                                })\n                        })\n                })\n            })\n        });\n    \n        it('deleteWithIntentId', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", intentsEngine: 'tiledesk-ai' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create chatbot res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", intent_id: uuidv4() })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create intent res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    let faqid = res.body.intent_id;\n    \n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/faq/intentId' + faqid + \"?id_faq_kb=\" + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"delete intent res.body\", res.body); }\n    \n                                            res.should.have.status(200);\n    \n                                            done();\n                                        })\n    \n                                })\n                        })\n                })\n            })\n        });\n    \n        it('deleteWithIntentIdError', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", intentsEngine: 'tiledesk-ai' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create chatbot res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", intent_id: uuidv4() })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create intent res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    let faqid = res.body.intent_id;\n    \n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/faq/intentId' + faqid)\n                                        // .delete('/' + savedProject._id + '/faq/intentId' + faqid + \"?id_faq_kb=\" + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"delete intent res.body\", res.body); }\n    \n                                            res.should.have.status(500);\n    \n                                            done();\n                                        })\n    \n                                })\n                        })\n                })\n            })\n        });\n    \n        it('deleteWithIntentIdErroWrongChatbotId', (done) => {\n    \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n    \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-search-faqs\", savedUser._id).then((savedProject) => {\n    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", intentsEngine: 'tiledesk-ai' })\n                        .end((err, res) => {\n    \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create chatbot res.body\", res.body); }\n    \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n    \n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq')\n                                .auth(email, pwd)\n                                .send({ id_faq_kb: id_faq_kb, question: \"question1\", answer: \"answer1\", intent_id: uuidv4() })\n                                .end((err, res) => {\n    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create intent res.body\", res.body); }\n    \n                                    res.should.have.status(200);\n                                    let faqid = res.body.intent_id;\n    \n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/faq/intentId' + faqid + \"?id_faq_kb=11233\")\n                                        // .delete('/' + savedProject._id + '/faq/intentId' + faqid + \"?id_faq_kb=\" + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n    \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"delete intent res.body\", res.body); }\n    \n                                            res.should.have.status(404);\n    \n                                            done();\n                                        })\n                                })\n                        })\n                })\n            })\n        });\n\n    })\n\n\n\n});\n\n"
  },
  {
    "path": "test/faqService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\n\nmongoose.connect(config.databasetest);\n\nvar leadService = require('../services/leadService');\nvar projectService = require(\"../services/projectService\");\nvar userService = require('../services/userService');\nvar faqService = require('../services/faqService');\nvar Faq = require('../models/faq');\nvar winston = require('../config/winston');\n\nlet log = false;\n\n\ndescribe('FaqService()', function () {\n\n  it('createee-and-search', (done) => {\n\n    var email = \"test-subscription-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"test-FaqService\", savedUser._id).then(function (savedProject) {\n        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\" }).then(function (savedBot) {\n\n          var newFaq = new Faq({\n            id_faq_kb: savedBot._id,\n            question: \"question\",\n            answer: \"answer\",\n            id_project: savedProject._id,\n            topic: \"default\",\n            createdBy: savedUser._id,\n            updatedBy: savedUser._id\n          });\n          \n\n          newFaq.save(function (err, savedFaq) {\n            winston.debug(\"err\", err);\n            winston.debug(\"resolve\", savedFaq);\n\n            expect(savedBot.name).to.equal(\"testbot\");\n            expect(savedBot.secret).to.not.equal(null);\n            expect(savedFaq.question).to.equal(\"question\");\n            expect(savedFaq.intent_id).to.not.equal(undefined);\n            expect(savedFaq.intent_display_name).to.not.equal(undefined);\n            expect(savedFaq.webhook_enabled).to.equal(false);\n\n            var query = { \"id_faq_kb\": savedBot._id };\n\n            // aggiunta qui \n            query.$text = { \"$search\": \"question\" };\n\n            return Faq.find(query, { score: { $meta: \"textScore\" } })\n              .sort({ score: { $meta: \"textScore\" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n              .lean()\n              .exec(function (err, faqs) {\n                if (log) { console.log(\"faqs\", faqs); }\n                // expect(faqs.length).to.equal(1);\n                expect(faqs[0]._id.toString()).to.equal(savedFaq._id.toString());\n                expect(faqs[0].answer).to.equal(\"answer\");\n                expect(faqs[0].question).to.equal(\"question\");\n                expect(faqs[0].score).to.not.equal(null);\n                \n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  it('create-with-intent_display_name-and-search', (done) => {\n\n    var email = \"test-subscription-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"test-FaqService\", savedUser._id).then(function (savedProject) {\n        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\" }).then(function (savedBot) {\n\n          var newFaq = new Faq({\n            id_faq_kb: savedBot._id,\n            question: \"question\",\n            answer: \"answer\",\n            id_project: savedProject._id,\n            topic: \"default\",\n            intent_display_name: \"question1\",\n            createdBy: savedUser._id,\n            updatedBy: savedUser._id\n          });\n\n          newFaq.save(function (err, savedFaq) {\n            winston.debug(\"err\", err);\n            winston.debug(\"resolve\", savedFaq);\n            expect(savedBot.name).to.equal(\"testbot\");\n            expect(savedBot.secret).to.not.equal(null);\n            expect(savedFaq.question).to.equal(\"question\");\n            expect(savedFaq.intent_id).to.not.equal(undefined);\n            expect(savedFaq.intent_display_name).to.equal(\"question1\");\n            expect(savedFaq.webhook_enabled).to.equal(false);\n\n            var query = { \"id_faq_kb\": savedBot._id };\n\n            // aggiunta qui \n            query.$text = { \"$search\": \"question\" };\n\n            return Faq.find(query, { score: { $meta: \"textScore\" } })\n              .sort({ score: { $meta: \"textScore\" } }) //https://docs.mongodb.com/manual/reference/operator/query/text/#sort-by-text-search-score\n              .lean().\n              exec(function (err, faqs) {\n                if (err) { console.error(\"err: \", err )}\n                if (log) { console.log(\"faqs\", faqs); }\n                // expect(faqs.length).to.equal(1);\n                expect(faqs[0]._id.toString()).to.equal(savedFaq._id.toString());\n                expect(faqs[0].answer).to.equal(\"answer\");\n                expect(faqs[0].question).to.equal(\"question\");\n                expect(faqs[0].score).to.not.equal(null);\n                \n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  it('create-with-duplicated-intent_display_name-and-search', (done) => {\n\n    var email = \"test-subscription-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"test-FaqService\", savedUser._id).then(function (savedProject) {\n        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\" }).then(function (savedBot) {\n\n          var newFaq0 = new Faq({\n            id_faq_kb: savedBot._id,\n            question: \"question\",\n            answer: \"answer\",\n            id_project: savedProject._id,\n            topic: \"default\",\n            intent_display_name: \"question1\",\n            createdBy: savedUser._id,\n            updatedBy: savedUser._id\n          });\n\n          newFaq0.save(function (err, savedFaq0) {\n\n            var newFaq = new Faq({\n              id_faq_kb: savedBot._id,\n              question: \"question\",\n              answer: \"answer\",\n              id_project: savedProject._id,\n              topic: \"default\",\n              intent_display_name: \"question1\",\n              createdBy: savedUser._id,\n              updatedBy: savedUser._id\n            });\n\n            newFaq.save(function (err, savedFaq) {\n\n              if (log) { console.log(\"err.code \", err.code); }\n              if (err.code == 11000) {\n                done()\n              }\n\n            });\n          });\n        });\n      });\n    });\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/faqkbRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'error';\n\nvar Faq = require('../models/faq');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nvar faqService = require('../services/faqService');\n\nlet chatbot_mock = require('./mock/chatbotMock');\nlet log = false;\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\nconst Project_user = require('../models/project_user');\nconst roleConstants = require('../models/roleConstants');\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('FaqKBRoute', () => {\n\n    describe('Get', () => {\n\n        it('get-all-chatbot-with-role-admin-or-owner', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"external\", language: 'fr' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq_kb')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    done();\n                                });\n                        });\n\n\n                });\n            });\n\n        }).timeout(20000);\n\n        it('get-all-chatbot-with-role-agent', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"external\", language: 'fr' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n\n                            Project_user.findOneAndUpdate({ id_project: savedProject._id, id_user: savedUser._id }, { role: roleConstants.AGENT }, (err, savedProject_user) => {\n                                chai.request(server)\n                                    .get('/' + savedProject._id + '/faq_kb')\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n\n                                        done();\n                                    });\n                            })\n\n                        });\n\n\n                });\n            });\n\n        }).timeout(20000);\n\n    })\n\n    describe('Create', () => {\n\n        it('create-new-chatbot', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"external\", language: 'en' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq_kb/' + res.body._id)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    done();\n\n                                });\n                        });\n                });\n            });\n\n        })\n\n        it('create-new-chatbot-auto-slug', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"My Awesome Bot\");\n                            expect(res.body.slug).to.equal(\"my-awesome-bot\")\n                            expect(res.body.language).to.equal(\"en\");\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq_kb')\n                                .auth(email, pwd)\n                                .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"My Awesome Bot\");\n                                    expect(res.body.slug).to.equal(\"my-awesome-bot-1\")\n                                    expect(res.body.language).to.equal(\"en\");\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + '/faq_kb')\n                                        .auth(email, pwd)\n                                        .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.name).to.equal(\"My Awesome Bot\");\n                                            expect(res.body.slug).to.equal(\"my-awesome-bot-2\")\n                                            expect(res.body.language).to.equal(\"en\");\n\n                                            chai.request(server)\n                                                .post('/' + savedProject._id + '/faq_kb')\n                                                .auth(email, pwd)\n                                                .send({ \"name\": \"My Awesome Bot 1\", type: \"internal\", language: 'en', template: \"blank\" })\n                                                .end((err, res) => {\n\n                                                    if (err) { console.error(\"err: \", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.name).to.equal(\"My Awesome Bot 1\");\n                                                    expect(res.body.slug).to.equal(\"my-awesome-bot-1-1\")\n                                                    expect(res.body.language).to.equal(\"en\");\n\n                                                    chai.request(server)\n                                                        .get('/' + savedProject._id + '/faq_kb/')\n                                                        .auth(email, pwd)\n                                                        .end((err, res) => {\n\n                                                            if (err) { console.error(\"err: \", err); }\n                                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                                            res.should.have.status(200);\n\n                                                            done();\n\n                                                        });\n                                                })\n                                        })\n                                })\n                        });\n                });\n            });\n        })\n\n        it('create-new-chatbot-agent-role', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n                    Project_user.findOneAndUpdate({ id_project: savedProject._id, id_user: savedUser._id }, { role: roleConstants.AGENT }, (err, savedProject_user) => {\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/faq_kb')\n                            .auth(email, pwd)\n                            .send({ \"name\": \"testbot\", type: \"external\", language: 'fr' })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(403);\n                                expect(res.body.success).to.equal(false);\n                                expect(res.body.msg).to.equal(\"you dont have the required role.\");\n\n                                done();\n\n                            });\n                    })\n                });\n            });\n\n        })\n\n        it('create-with-template-example', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n\n                            setTimeout(() => {\n\n                                chai.request(server)\n                                    .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n    \n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n    \n                                        res.should.have.status(200);\n                                        res.body.should.be.an('array').that.is.not.empty;\n    \n                                        done();\n    \n                                    })\n                            }, 1000)\n\n\n\n                        });\n                })\n            })\n        })\n\n        it('create-with-template-blank', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"blank\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"faq_list: \", JSON.stringify(res.body, null, 2)); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array').that.is.not.empty;\n\n                                    done();\n\n                                })\n\n\n\n                        });\n                })\n            })\n        })\n\n        it('create-new-webhook', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ name: \"testflow\", type: \"tilebot\", subtype: \"webhook\", language: 'en' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testflow\");\n                            expect(res.body.language).to.equal(\"en\");\n                            expect(res.body.type).to.equal(\"tilebot\");\n                            expect(res.body.subtype).to.equal(\"webhook\")\n\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq/?id_faq_kb=' + res.body._id)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    done();\n\n                                });\n                        });\n                });\n            });\n\n        })\n\n    });\n\n    describe('Update', () => {\n\n        it('update-chatbot-no-slug', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"My Awesome Bot\");\n                            expect(res.body.slug).to.equal(\"my-awesome-bot\")\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .put('/' + savedProject._id + '/faq_kb/' + id_faq_kb)\n                                .auth(email, pwd)\n                                .send({ \"name\": \"My Magician Bot\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"My Magician Bot\");\n                                    expect(res.body.slug).to.equal(\"my-awesome-bot\");\n\n                                    done()\n                                })\n\n                        });\n                });\n            });\n        })\n\n        it('update-chatbot-slug-with-existing-one', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"My Awesome Bot\");\n                            expect(res.body.slug).to.equal(\"my-awesome-bot\")\n                            expect(res.body.language).to.equal(\"en\");\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq_kb')\n                                .auth(email, pwd)\n                                .send({ \"name\": \"My Awesome Bot\", type: \"internal\", language: 'en', template: \"blank\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"My Awesome Bot\");\n                                    expect(res.body.slug).to.equal(\"my-awesome-bot-1\")\n                                    expect(res.body.language).to.equal(\"en\");\n\n                                    let id_faq_kb = res.body._id;\n\n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/faq_kb/' + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .send({ \"slug\": \"my-awesome-bot\" })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(500);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(false);\n                                            expect(res.body.error).to.equal(\"Slug already exists: my-awesome-bot\");\n                                            expect(res.body.error_code).to.equal(12001);\n\n                                            done()\n\n                                        })\n\n                                });\n\n                        });\n                });\n            });\n        })\n\n        it('update-chatbot-and-intents-language', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: \"en\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            var id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array').that.is.not.empty;\n\n\n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/faq_kb/' + id_faq_kb + '/language/it')\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.name).to.equal(\"testbot\");\n                                            expect(res.body.language).to.equal(\"it\");\n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                                .auth(email, pwd)\n                                                .end((err, res) => {\n\n                                                    if (err) { console.error(\"err: \", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.an('array').that.is.not.empty;\n\n                                                    done();\n                                                })\n\n                                        })\n\n                                })\n\n\n\n                        });\n                })\n            })\n        })\n\n    })\n\n    describe('Import/Export and Fork', () => {\n\n        it('fork-chatbot-private', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"current-project\", savedUser._id).then(function (currentProject) {\n                    projectService.create(\"landing-project\", savedUser._id).then(function (landingProject) {\n\n                        class chatbot_service {\n                            async getBotById(id, published, api_url, chatbot_templates_api_url, token, project_id) {\n                                return chatbot_mock.existing_chatbot_mock;\n                            }\n\n                            async createBot(api_url, token, chatbot, project_id) {\n                                return chatbot_mock.empty_chatbot_mock\n                            }\n\n                            async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {\n                                return chatbot_mock.import_faqs_res_mock\n                            }\n                        }\n\n                        server.set('chatbot_service', new chatbot_service());\n\n\n                        chai.request(server)\n                            .post('/' + currentProject._id + '/faq_kb')\n                            .auth(email, pwd)\n                            .send({ \"name\": \"privateBot\", type: \"internal\", language: 'en', public: \"false\", template: \"blank\" })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n                                expect(res.body.name).to.equal(\"privateBot\");\n                                expect(res.body.language).to.equal(\"en\");\n                                let id_faq_kb = res.body._id;\n\n                                chai.request(server)\n                                    .post('/' + currentProject._id + '/faq_kb/fork/' + id_faq_kb + \"?public=false&projectid=\" + landingProject._id)\n                                    .auth(email, pwd)\n                                    .set('Content-Type', 'application/json')\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n                                        res.should.have.status(200);\n                                        res.should.have.be.a('object');\n                                        expect(res.body.bot_id).to.equal(chatbot_mock.empty_chatbot_mock._id)\n\n                                        done();\n\n                                    })\n                            })\n                    })\n                })\n            })\n\n\n        })\n\n        it('fork-chatbot-private-not-permitted', (done) => {\n            var email_user1 = \"user1-signup-\" + Date.now() + \"@email.com\";\n            var email_user2 = \"user2-signup-\" + (Date.now() + 1) + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email_user1, pwd, \"User1 Firstname\", \"User1 lastname\").then(function (user1) {\n                userService.signup(email_user2, pwd, \"User2 Firstname\", \"User2 lastname\").then(function (user2) {\n                    projectService.create(\"project1\", user1._id).then(function (currentProject) {\n                        projectService.create(\"project2\", user2._id).then(function (landingProject) {\n\n                            class chatbot_service {\n                                async getBotById(id, published, api_url, chatbot_templates_api_url, token, project_id) {\n                                    return new Promise((resolve, reject) => {\n                                        reject({ success: false, msg: \"Chatbot not found\" })\n                                    })\n                                }\n    \n                                async createBot(api_url, token, chatbot, project_id) {\n                                    return chatbot_mock.empty_chatbot_mock\n                                }\n    \n                                async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {\n                                    return chatbot_mock.import_faqs_res_mock\n                                }\n                            }\n\n                            server.set('chatbot_service', new chatbot_service());\n\n                            chai.request(server)\n                                .post('/' + currentProject._id + '/faq_kb')\n                                .auth(email_user1, pwd)\n                                .send({ \"name\": \"chatbot1\", type: \"tilebot\", language: 'en', public: \"false\", template: \"blank\" })\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"chatbot1\");\n                                    expect(res.body.language).to.equal(\"en\");\n                                    expect(res.body.public).to.equal(false);\n\n                                    const chatbot_id = res.body._id;\n\n                                    chai.request(server)\n                                        .post('/' + landingProject._id + '/faq_kb/fork/' + chatbot_id + \"?public=false&projectid=\" + landingProject._id)\n                                        .auth(email_user2, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body: \", res.body); }\n                                            \n                                            res.should.have.status(500);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(false);\n                                            expect(res.body.error).to.equal(\"Unable to get chatbot to be forked\");\n                                            done();\n                                        })\n\n                                });\n                        });\n                    });\n                });\n            });\n        });\n\n        it('fork-chatbot-public', (done) => {\n            var email_user1 = \"user1-signup-\" + Date.now() + \"@email.com\";\n            var email_user2 = \"user2-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email_user1, pwd, \"User1 Firstname\", \"User1 lastname\").then(function (user1) {\n                userService.signup(email_user2, pwd, \"User2 Firstname\", \"User2 lastname\").then(function (user2) {\n                    projectService.create(\"current-project\", user1._id).then(function (currentProject) {\n                        projectService.create(\"landing-project\", user2._id).then(function (landingProject) {\n\n                            if (log) { console.log(\"mock: \", chatbot_mock.existing_chatbot_mock); }\n\n                            class chatbot_service {\n                                async getBotById(id, published, api_url, chatbot_templates_api_url, token, project_id) {\n                                    return chatbot_mock.existing_chatbot_mock;\n                                }\n\n                                async createBot(api_url, token, chatbot, project_id) {\n                                    return chatbot_mock.empty_chatbot_mock\n                                }\n\n                                async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {\n                                    return chatbot_mock.import_faqs_res_mock\n                                }\n                            }\n\n                            server.set('chatbot_service', new chatbot_service());\n\n                            chai.request(server)\n                                .post('/' + currentProject._id + '/faq_kb')\n                                .auth(email_user1, pwd)\n                                .send({ \"name\": \"publicBot\", type: \"internal\", language: 'en', public: \"true\", template: \"blank\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"publicBot\");\n                                    expect(res.body.language).to.equal(\"en\");\n                                    let id_faq_kb = res.body._id;\n\n                                    chai.request(server)\n                                        .post('/' + landingProject._id + '/faq_kb/fork/' + id_faq_kb + '?public=true&projectid=' + landingProject._id)\n                                        .auth(email_user2, pwd)\n                                        .set('Content-Type', 'application/json')\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n\n                                            done();\n                                        })\n                                })\n                        })\n                    })\n                })\n            })\n        })\n\n        it('create-bot-and-import-json', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb/importjson/' + null + \"?create=true\")\n                        .auth(email, pwd)\n                        .set('Content-Type', 'text/plain')\n                        .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-json-rules.txt')), 'example-json-rules')\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.should.be.a('object');\n                            expect(res.body.name).to.equal(\"example bot\");\n                            expect(res.body.slug).to.equal(\"my-example-bot\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                            .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                //res.body.should.be.an('array').that.is.not.empty;\n\n                                done();\n\n                            })\n                        })\n\n                })\n            })\n\n        })\n\n        it('import-json-in-an-existing-bot', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then(((savedUser) => {\n                projectService.create('test-faqkb-create', savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank \" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"en\");\n                            let id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb)\n                                .auth(email, pwd)\n                                .set('Content-Type', 'text/plain')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-json-rules.txt')), 'example-json-rules')\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"import json res: \", JSON.stringify(res.body, null, 2)); }\n\n                                    res.should.have.status(200);\n                                    //res.should.be.a('object');\n                                    //expect(res.body.name).to.equal(\"examplebot\");\n                                    //expect(res.body.language).to.equal(\"en\");\n\n                                    done();\n                                })\n                        })\n                })\n            }))\n        })\n\n        it('import-json-in-an-existing-bot-and-replace-all-intents', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then(((savedUser) => {\n                projectService.create('test-faqkb-create', savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        //.post('/' + savedProject._id + '/faq_kb?replace=true')\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"empty\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"en\");\n                            let id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq/?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200)\n                                    res.body.should.be.a('array');\n                                    expect(res.body.length).to.equal(0);\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .set('Content-Type', 'text/plain')\n                                        .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-json-rules.txt')), 'example-json-rules')\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"import json res: \", JSON.stringify(res.body, null, 2)); }\n                                            \n                                            res.should.have.status(200);\n                                            res.should.be.a('object');\n                                            expect(res.body.name).to.equal(\"example bot\");\n                                            expect(res.body.language).to.equal(\"en\");\n\n                                            done();\n                                        })\n                                })\n\n                        })\n                })\n            }))\n        })\n\n        it('import-json', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", language: 'fr', template: \"blank\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n                            let id_faq_kb = res.body._id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb)\n                                .auth(email, pwd)\n                                .set('Content-Type', 'text/plain')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-json.txt')), 'example-json.txt')\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.should.be.a('object');\n                                    expect(res.body.name).to.equal(\"examplebot\");\n                                    expect(res.body.language).to.equal(\"en\");\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err) };\n                                            if (log) { console.log(\"res.body: \", res.body) };\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.an('array').that.is.not.empty;\n\n                                            done();\n\n                                        })\n                                })\n                        })\n                })\n            })\n        })\n\n        it('export-json', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: 'fr' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n                            let id_faq_kb = res.body._id;\n\n                            if (log) { console.log(\"id_faq_kb: \", id_faq_kb); }\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array').that.is.not.empty;\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/faq_kb/exportjson/' + id_faq_kb)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err) };\n                                            if (log) { console.log(\"res.body: \", res.body) };\n                                            \n                                            res.should.have.status(200);\n                                            //res.body.should.be.a('string');\n\n                                            done();\n                                        })\n                                })\n\n                            // chai.request(server)\n                            //     .patch('/' + savedProject._id + '/faq_kb/' + id_faq_kb + '/attributes')\n                            //     .auth(email, pwd)\n                            //     .send({ variables: { var1: \"var1\", var2: \"var2\" },  globals: [{ key: 'test', value: 'test]'}] })\n                            //     .end((err, res) => {\n                            //         console.log(\"res.body: \", res.body)\n\n\n                            //     })\n\n                        });\n                });\n            });\n\n        }).timeout(20000);\n\n        it('export-json-intents-only)', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: 'fr' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"fr\");\n                            let id_faq_kb = res.body._id;\n                            if (log) { console.log(\"id_faq_kb: \", id_faq_kb); }\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array').that.is.not.empty;\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/faq_kb/exportjson/' + id_faq_kb + \"?intentsOnly=true\")\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err) };\n                                            if (log) { console.log(\"res.body: \", res.body) };\n\n                                            res.should.have.status(200);\n                                            //res.body.should.be.a('string');\n\n                                            done();\n                                        })\n                                })\n                        });\n                });\n            });\n\n        }).timeout(20000);\n\n        it('import-webhook-json', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb/importjson/null?create=true')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'text/plain')\n                        .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-webhook-json.txt')), 'example-webhook-json.txt')\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.should.be.a('object');\n                            expect(res.body.name).to.equal(\"Flow 1\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let id_faq_kb = res.body._id\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.an('array').that.is.not.empty;\n                                    expect(res.body.length).to.equal(3);\n\n                                    done();\n\n                                })\n                        })\n                })\n            })\n        })\n        \n\n    })\n\n    describe('Delete', () => {\n\n        it('logical-delete-with-ttl', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ name: \"testbot\", type: \"tilebot\", subtype: \"chatbot\", template: \"blank\", language: 'en' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"testbot\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let chatbot_id = res.body._id;\n\n                            chai.request(server)\n                                .put('/' + savedProject._id + '/faq_kb/' + chatbot_id)\n                                .auth(email, pwd)\n                                .send({ trashed: true })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.trashed).to.equal(true);\n                                    expect(res.body.trashedAt).to.exist;\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/faq/?id_faq_kb=' + chatbot_id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n                                            \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('array');\n                                            expect(res.body.length).to.equal(3);\n                                            expect(res.body[0].trashed).to.equal(true);\n                                            expect(res.body[0].trashedAt).to.exist;\n\n                                            done();\n                                        })\n\n                                })\n                        });\n                });\n            });\n        })\n\n        it('logical-webhook-delete-with-ttl', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ name: \"test-webhook\", type: \"tilebot\", subtype: \"webhook\", template: \"blank_webhook\", language: 'en' })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.name).to.equal(\"test-webhook\");\n                            expect(res.body.language).to.equal(\"en\");\n\n                            let chatbot_id = res.body._id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/webhooks')\n                                .auth(email, pwd)\n                                .send({ chatbot_id: chatbot_id, block_id: \"example-block-id\", async: false })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/faq_kb/' + chatbot_id)\n                                        .auth(email, pwd)\n                                        .send({ trashed: true })\n                                        .end((err, res) => {\n        \n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n        \n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.trashed).to.equal(true);\n                                            expect(res.body.trashedAt).to.exist;\n        \n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/faq/?id_faq_kb=' + chatbot_id)\n                                                .auth(email, pwd)\n                                                .end((err, res) => {\n                                                    \n                                                    if (err) { console.error(\"err: \", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n        \n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('array');\n                                                    expect(res.body.length).to.equal(2);\n                                                    expect(res.body[0].trashed).to.equal(true);\n                                                    expect(res.body[0].trashedAt).to.exist;\n        \n                                                    done();\n                                                })\n        \n                                        })\n                                })\n\n                        });\n                });\n            });\n        })\n    })\n\n    // describe('Train', () => {\n\n    //     it('train', (done) => {\n\n    //         var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    //         var pwd = \"pwd\";\n\n    //         userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n    //             projectService.create(\"test-faqkb-train\", savedUser._id).then(function (savedProject) {\n    //                 faqService.create(\"testbot\", \"http://54.228.177.1644\", savedProject._id, savedUser._id).then(function (savedBot) {\n\n    //                     var newFaq = new Faq({\n    //                         id_faq_kb: savedBot._id,\n    //                         question: \"question1\\nquestion2\",\n    //                         answer: \"answer\",\n    //                         id_project: savedProject._id,\n    //                         topic: \"default\",\n    //                         createdBy: savedUser._id,\n    //                         updatedBy: savedUser._id\n    //                     });\n\n    //                     newFaq.save(function (err, savedFaq) {\n    //                         expect(savedBot.name).to.equal(\"testbot\");\n    //                         expect(savedBot.secret).to.not.equal(null);\n\n    //                         chai.request(server)\n    //                             .post('/' + savedProject._id + '/faq_kb/train')\n    //                             .auth(email, pwd)\n    //                             .send({ \"id_faq_kb\": savedBot._id })\n    //                             .end((err, res) => {\n\n    //                                 if (err) { console.error(\"err: \", err) };\n    //                                 if (log) { console.log(\"res.body: \", res.body) };\n\n    //                                 res.should.have.status(200);\n    //                                 res.body.should.be.a('object');\n    //                                 expect(res.body.train.nlu.intent).to.equal(savedBot.intent_display_name);\n    //                                 // expect(res.body.text).to.equal(\"addestramento avviato\");         \n\n\n    //                                 done();\n    //                             });\n\n\n    //                     });\n    //                 });\n    //             });\n    //         });\n    //     }).timeout(20000);\n\n    // })\n\n});\n\n\n/**\n* This test will be no longer available after merge with master because\n* the profile section can no longer be modified via api.\n*/\n// it('createMaximumNumberExceeded', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n//             chai.request(server)\n//                 .put('/projects/' + savedProject._id)\n//                 .auth(email, pwd)\n//                 .send({ profile: { quotes: { chatbots: 2 } } })\n//                 .end((err, res) => {\n\n//                     if (log) { console.log(\"res.body\", res.body); }\n\n//                     chai.request(server)\n//                         .post('/' + savedProject._id + '/faq_kb')\n//                         .auth(email, pwd)\n//                         .send({ \"name\": \"testbot1\", type: \"external\", language: 'en' })\n//                         .end((err, res) => {\n//                             if (log) { console.log(\"res.body\", res.body); }\n//                             res.should.have.status(200);\n//                             res.body.should.be.a('object');\n//                             expect(res.body.name).to.equal(\"testbot1\");\n//                             expect(res.body.language).to.equal(\"en\");\n\n//                             chai.request(server)\n//                                 .post('/' + savedProject._id + '/faq_kb')\n//                                 .auth(email, pwd)\n//                                 .send({ \"name\": \"testbot2\", type: \"external\", language: 'en' })\n//                                 .end((err, res) => {\n//                                     if (log) { console.log(\"res.body\", res.body); }\n//                                     res.should.have.status(200);\n//                                     res.body.should.be.a('object');\n//                                     expect(res.body.name).to.equal(\"testbot2\");\n//                                     expect(res.body.language).to.equal(\"en\");\n\n//                                     chai.request(server)\n//                                         .post('/' + savedProject._id + '/faq_kb')\n//                                         .auth(email, pwd)\n//                                         .send({ \"name\": \"testbot3\", type: \"external\", language: 'en' })\n//                                         .end((err, res) => {\n\n//                                             if (log) { console.log(\"res.body\", res.body); }\n\n//                                             res.should.have.status(403);\n//                                             res.body.should.be.a('object');\n//                                             expect(res.body.success).to.equal(false);\n//                                             expect(res.body.error).to.equal(\"Maximum number of chatbots reached for the current plan\");\n//                                             expect(res.body.plan_limit).to.equal(2);\n\n//                                             done()\n\n//                                         });\n//                                 });\n//                         });\n//                 })\n//         });\n//     });\n\n// }).timeout(20000);\n\n\n// it('train with tiledesk-ai', (done) => {\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then((savedProject) => {\n\n//             chai.request(server)\n//                 .post('/' + savedProject._id + '/faq_kb')\n//                 .auth(email, pwd)\n//                 .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", intentsEngine: \"tiledesk-ai\" })\n//                 .end((err, res) => {\n//                     if (log) {\n//                         console.log(\"res.body\", res.body);\n//                     }\n//                     res.should.have.status(200);\n//                     res.body.should.be.a('object');\n//                     expect(res.body.name).to.equal(\"testbot\");\n//                     var id_faq_kb = res.body._id;\n\n//                     chai.request(server)\n//                         .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n//                         .auth(email, pwd)\n//                         .end((err, res) => {\n//                             if (log) { console.log(\"faq_list: \", res.body); }\n//                             res.should.have.status(200);\n//                             res.body.should.be.an('array').that.is.not.empty;\n\n//                             chai.request(server)\n//                                 .post('/' + savedProject._id + '/faq_kb/aitrain')\n//                                 .auth(email, pwd)\n//                                 .send({ id_faq_kb: id_faq_kb, webhook_enabled: false })\n//                                 .end((err, res) => {\n//                                     if (log) { console.log(\"train res.body: \", res.body); }\n\n//                                     done();\n//                                 })\n\n//                         })\n\n\n\n//                 });\n//         })\n//     })\n// })\n\n\n// DEPRECATED\n// it('import json (simple)', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n//             chai.request(server)\n//                 .post('/' + savedProject._id + '/faq_kb')\n//                 .auth(email, pwd)\n//                 .send({ \"name\": \"testbot\", type: \"internal\", language: 'fr' })\n//                 .end((err, res) => {\n//                     if (log) {\n//                         console.log(\"res.body: \", res.body);\n//                     }\n//                     res.should.have.status(200);\n//                     res.body.should.be.a('object');\n//                     expect(res.body.name).to.equal(\"testbot\");\n//                     expect(res.body.language).to.equal(\"fr\");\n//                     let id_faq_kb = res.body._id;\n\n//                     chai.request(server)\n//                         .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb + '?intentsOnly=true&overwrite=true')\n//                         .auth(email, pwd)\n//                         .set('Content-Type', 'text/plain')\n//                         .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './example-json-intents.txt')), 'example-json-intents.txt')\n//                         .end((err, res) => {\n//                             if (log) {\n//                                 console.log(\"import (intents only) json res: \", res.body);\n//                             }\n//                             res.should.have.status(200);\n//                             //res.should.be.a('object');\n//                             //expect(res.body.success).to.equal(true);\n\n//                             chai.request(server)\n//                                 .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n//                                 .auth(email, pwd)\n//                                 .end((err, res) => {\n//                                     if (log) {\n//                                         console.log(\"faq_list: \", res.body);\n//                                     }\n//                                     res.should.have.status(200);\n//                                     res.body.should.be.an('array').that.is.not.empty;\n\n//                                     done();\n\n//                                 })\n//                         })\n//                 })\n//         })\n//     })\n// })\n\n\n// DEPRECATED\n// it('import json (intents only) (overwrite true)', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n//             chai.request(server)\n//                 .post('/' + savedProject._id + '/faq_kb')\n//                 .auth(email, pwd)\n//                 .send({ \"name\": \"testbot\", type: \"internal\", language: 'fr', template: 'blank' })\n//                 .end((err, res) => {\n//                     if (log) {\n//                         console.log(\"res.body: \", res.body);\n//                     }\n//                     res.should.have.status(200);\n//                     res.body.should.be.a('object');\n//                     expect(res.body.name).to.equal(\"testbot\");\n//                     expect(res.body.language).to.equal(\"fr\");\n//                     let id_faq_kb = res.body._id;\n\n//                     chai.request(server)\n//                         .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb + '?intentsOnly=true&overwrite=true')\n//                         .auth(email, pwd)\n//                         .set('Content-Type', 'text/plain')\n//                         .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './example-json-intents.txt')), 'example-json-intents.txt')\n//                         .end((err, res) => {\n//                             if (log) {\n//                                 console.log(\"import (intents only) json res: \", res.body);\n//                             }\n//                             res.should.have.status(200);\n//                             //res.should.be.a('object');\n//                             //expect(res.body.success).to.equal(true);\n\n//                             chai.request(server)\n//                                 .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n//                                 .auth(email, pwd)\n//                                 .end((err, res) => {\n//                                     if (log) {\n//                                         console.log(\"faq_list: \", res.body);\n//                                     }\n//                                     res.should.have.status(200);\n//                                     res.body.should.be.an('array').that.is.not.empty;\n\n//                                     done();\n\n//                                 })\n//                         })\n//                 })\n//         })\n//     })\n// })\n\n\n// DEPRECATED\n// it('import json (intents only) (overwrite false)', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n//             chai.request(server)\n//                 .post('/' + savedProject._id + '/faq_kb')\n//                 .auth(email, pwd)\n//                 .send({ \"name\": \"testbot\", type: \"internal\", language: 'fr', template: 'blank' })\n//                 .end((err, res) => {\n//                     if (log) {\n//                         console.log(\"res.body: \", res.body);\n//                     }\n//                     res.should.have.status(200);\n//                     res.body.should.be.a('object');\n//                     expect(res.body.name).to.equal(\"testbot\");\n//                     expect(res.body.language).to.equal(\"fr\");\n//                     let id_faq_kb = res.body._id;\n\n//                     chai.request(server)\n//                         .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb + '?intentsOnly=true')\n//                         .auth(email, pwd)\n//                         .set('Content-Type', 'text/plain')\n//                         .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './example-json-intents.txt')), 'example-json-intents.txt')\n//                         .end((err, res) => {\n//                             if (log) {\n//                                 console.log(\"import (intents only) json res: \", res.body);\n//                             }\n//                             res.should.have.status(200);\n//                             //res.should.be.a('object');\n//                             //expect(res.body.success).to.equal(true);\n\n//                             chai.request(server)\n//                                 .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n//                                 .auth(email, pwd)\n//                                 .end((err, res) => {\n//                                     if (log) {\n//                                         console.log(\"faq_list: \", res.body);\n//                                     }\n//                                     res.should.have.status(200);\n//                                     res.body.should.be.an('array').that.is.not.empty;\n\n//                                     done();\n\n//                                 })\n//                         })\n//                 })\n//         })\n//     })\n// })\n\n// it('publishChatbot', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then(function (savedUser) {\n//         projectService.create(\"current-project\", savedUser._id).then(function (currentProject) {\n\n//             console.log(\"declare chatbot_service functions...\")\n\n//             class chatbot_service {\n//                 async fork(id, api_url, token, project_id) {\n//                     console.log(\"chatbot_service test fork called\")\n//                     return { message: \"Chatbot forked successfully\", bot_id: savedChatbot._id }\n//                     //return chatbot_mock.existing_chatbot_mock;\n//                 }\n\n//                 async getBotById(id, published, api_url, chatbot_templates_api_url, token, project_id, globals) {\n//                     return chatbot_mock.existing_chatbot_mock;\n//                 }\n\n//                 async createBot(api_url, token, chatbot, project_id) {\n//                     return chatbot_mock.empty_chatbot_mock\n//                 }\n\n//                 async importFaqs(api_url, id_faq_kb, token, chatbot, project_id) {\n//                     return chatbot_mock.import_faqs_res_mock\n//                 }\n//             }\n\n//             server.set('chatbot_service', new chatbot_service());\n//             console.log(\"chatbot_service functions declared\")\n\n//             chai.request(server)\n//                     .post('/' + currentProject._id + '/faq_kb')\n//                     .auth(email, pwd)\n//                     .send({ \"name\": \"privateBot\", type: \"internal\", language: 'en', public: \"false\", template: \"blank\" })\n//                     .end((err, res) => {\n//                         console.log(\"res.body: \", res.body);\n//                         if (log) {\n//                         }\n//                         res.should.have.status(200);\n//                         res.body.should.be.a('object');\n//                         expect(res.body.name).to.equal(\"privateBot\");\n//                         expect(res.body.language).to.equal(\"en\");\n//                         let id_faq_kb = res.body._id;\n\n//                         chai.request(server)\n//                         .put('/' + currentProject._id + '/faq_kb/' +  id_faq_kb + '/publish')\n//                         .auth(email, pwd)\n//                         .set('Content-Type', 'application/json')\n//                         .end((err, res) => {\n//                             console.log(\"publish bot res.body: \", res.body);\n//                             res.should.have.status(200);\n\n//                             done();\n//                         })\n//                     })\n\n\n//         })\n//     })\n// })\n\n// DEPRECATED\n// it('import-json-overwrite-true', (done) => {\n\n//     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n//     var pwd = \"pwd\";\n\n//     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n//         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n\n//             chai.request(server)\n//                 .post('/' + savedProject._id + '/faq_kb')\n//                 .auth(email, pwd)\n//                 .send({ \"name\": \"testbot\", type: \"internal\", language: 'fr', template: \"blank\" })\n//                 .end((err, res) => {\n\n//                     if (err) { console.error(\"err: \", err) };\n//                     if (log) { console.log(\"res.body: \", res.body) };\n\n//                     res.should.have.status(200);\n//                     res.body.should.be.a('object');\n//                     expect(res.body.name).to.equal(\"testbot\");\n//                     expect(res.body.language).to.equal(\"fr\");\n//                     let id_faq_kb = res.body._id;\n\n//                     chai.request(server)\n//                         .post('/' + savedProject._id + '/faq_kb/importjson/' + id_faq_kb + \"?overwrite=true\")\n//                         .auth(email, pwd)\n//                         .set('Content-Type', 'text/plain')\n//                         .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './example.json')), 'example.json')\n//                         .end((err, res) => {\n\n//                             if (err) { console.error(\"err: \", err) };\n//                             if (log) { console.log(\"res.body: \", res.body) };\n\n//                             res.should.have.status(200);\n//                             res.should.be.a('object');\n//                             expect(res.body.name).to.equal(\"examplebot\");\n//                             expect(res.body.language).to.equal(\"en\");\n\n//                             chai.request(server)\n//                                 .get('/' + savedProject._id + '/faq?id_faq_kb=' + id_faq_kb)\n//                                 .auth(email, pwd)\n//                                 .end((err, res) => {\n\n//                                     if (err) { console.error(\"err: \", err) };\n//                                     if (log) { console.log(\"res.body: \", res.body) };\n\n//                                     res.should.have.status(200);\n//                                     res.body.should.be.an('array').that.is.not.empty;\n\n//                                     done();\n\n//                                 })\n//                         })\n//                 })\n//         })\n//     })\n// })"
  },
  {
    "path": "test/fileFilter.test.js",
    "content": "const { expect } = require('chai');\nconst path = require('path');\nconst mime = require('mime-types');\nlet log = false;\n\ndescribe('File Filter Tests', () => {\n  \n  // Simula la funzione getMimeTypes del tuo codice\n  function getMimeTypes(allowed_extension) {\n    const extension = allowed_extension.split(',').map(e => e.trim().toLowerCase());\n    const allowedMimeTypes = extension.map(ext => mime.lookup(ext)).filter(Boolean);\n    return allowedMimeTypes;\n  }\n\n  // Simula il fileFilter\n  const createFileFilter = (extensionsSource = 'default') => {\n    return (req, file, cb) => {\n      const default_allowed_extensions = \".jpg,.jpeg,.png,.gif,.pdf,.txt\";\n      const assets_allowed_extensions = \".jpg,.jpeg,.png,.gif,.pdf,.txt,.csv,.doc,.docx,.xls,.xlsx\";\n      \n      let allowed_extensions;\n      let allowed_mime_types;\n\n      if (extensionsSource === 'assets') {\n        allowed_extensions = assets_allowed_extensions;\n      } else {\n        allowed_extensions = default_allowed_extensions;\n      }\n\n      if (allowed_extensions !== \"*/*\") {\n        allowed_mime_types = getMimeTypes(allowed_extensions);\n        const ext = path.extname(file.originalname).toLowerCase();\n\n        if (!allowed_extensions.includes(ext)) {\n          const error = new Error(\"Extension not allowed\");\n          error.status = 403;\n          return cb(error);\n        }\n\n        const expectedMimetype = mime.lookup(ext);\n        if (expectedMimetype && file.mimetype !== expectedMimetype) {\n            const error = new Error(\"Mimetype mismatch detected\");\n            error.status = 403;\n            return cb(error);\n        }\n        \n        return cb(null, true);\n      } else {\n        return cb(null, true);\n      }\n    }\n  };\n\n  describe('getMimeTypes', () => {\n    it('should convert extensions to mime types', () => {\n      const result = getMimeTypes('.pdf,.png,.jpg');\n      if (log) { console.log('Mime types result:', result) };\n      expect(result).to.include('application/pdf');\n      expect(result).to.include('image/png');\n      expect(result).to.include('image/jpeg');\n    });\n\n    it('should handle extensions without dot', () => {\n      const result = getMimeTypes('pdf,png');\n      if (log) { console.log('Mime types without dot:', result) };\n      expect(result).to.have.lengthOf(2); // mime.lookup('pdf') returns false\n    });\n  });\n\n  describe('FileFilter - Extension Check', () => {\n    it('should reject file with disallowed extension', (done) => {\n      const fileFilter = createFileFilter('default');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'test.exe',\n        mimetype: 'application/x-msdownload'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        expect(error).to.exist;\n        expect(error.message).to.equal('Extension not allowed');\n        expect(error.status).to.equal(403);\n        done();\n      });\n    });\n\n    it('should accept file with allowed extension', (done) => {\n      const fileFilter = createFileFilter('default');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'test.pdf',\n        mimetype: 'application/pdf'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        expect(error).to.not.exist;\n        expect(result).to.be.true;\n        done();\n      });\n    });\n  });\n\n  describe('FileFilter - Mimetype Check', () => {\n    it('should reject PNG file with PDF mimetype (mismatch)', (done) => {\n        const fileFilter = createFileFilter('default');\n        const mockReq = {};\n        const mockFile = {\n          originalname: 'image.png',\n          mimetype: 'application/pdf'\n        };\n      \n        // DEBUG: aggiungi questo\n        const default_allowed_extensions = \".jpg,.jpeg,.png,.gif,.pdf,.txt\";\n        const allowed_mime_types = getMimeTypes(default_allowed_extensions);\n        if (log) { console.log('Allowed extensions:', default_allowed_extensions) };\n        if (log) { console.log('Allowed mime types:', allowed_mime_types) };\n        if (log) { console.log('File mimetype:', mockFile.mimetype) };\n        if (log) { console.log('Includes check:', allowed_mime_types.includes(mockFile.mimetype)) };\n      \n        fileFilter(mockReq, mockFile, (error, result) => {\n          if (log) { console.log('Error:', error) };\n          if (log) { console.log('Result:', result) };\n          expect(error).to.exist;\n          expect(error.message).to.equal('Mimetype mismatch detected');\n          expect(error.status).to.equal(403);\n          done();\n        });\n      });\n\n    it('should reject PDF file with HTML mimetype (mismatch)', (done) => {\n      const fileFilter = createFileFilter('default');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'document.pdf',\n        mimetype: 'text/html'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        if (log) { console.log('Error:', error) };\n        if (log) { console.log('Result:', result) };\n        expect(error).to.exist;\n        expect(error.message).to.equal('Mimetype mismatch detected');\n        done();\n      });\n    });\n\n    it('should accept matching extension and mimetype', (done) => {\n      const fileFilter = createFileFilter('default');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'image.png',\n        mimetype: 'image/png'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        expect(error).to.not.exist;\n        expect(result).to.be.true;\n        done();\n      });\n    });\n  });\n\n  describe('FileFilter - Assets Route', () => {\n    it('should accept CSV files on assets route', (done) => {\n      const fileFilter = createFileFilter('assets');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'data.csv',\n        mimetype: 'text/csv'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        expect(error).to.not.exist;\n        expect(result).to.be.true;\n        done();\n      });\n    });\n\n    it('should reject CSV files on chat route', (done) => {\n      const fileFilter = createFileFilter('default');\n      const mockReq = {};\n      const mockFile = {\n        originalname: 'data.csv',\n        mimetype: 'text/csv'\n      };\n\n      fileFilter(mockReq, mockFile, (error, result) => {\n        expect(error).to.exist;\n        expect(error.message).to.equal('Extension not allowed');\n        done();\n      });\n    });\n  });\n});"
  },
  {
    "path": "test/fileRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nvar userService = require('../services/userService');\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('FileRoute', () => {\n\n    describe('/post', () => {\n\n        it('post-user', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .post('/files/users/')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'application/pdf')\n                    .attach('file', fs.readFileSync('./test/fixtures/sample.pdf'), 'sample.pdf')\n                    // .field('delimiter', ';')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('File uploded successfully');\n                        expect(res.body.filename).to.not.equal(null);\n                        expect(res.body.thumbnail).to.not.equal(null);\n                        \n                        done();\n                    });\n            });\n        }).timeout(5000)\n\n\n\n        it('post-public', (done) => {\n\n\n            chai.request(server)\n                .post('/files/public/')\n                .set('Content-Type', 'application/pdf')\n                .attach('file', fs.readFileSync('./test/fixtures/sample.pdf'), 'sample.pdf')\n                .field('delimiter', ';')\n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(201);\n                    res.body.should.be.a('object');\n                    expect(res.body.message).to.equal('File uploded successfully');\n                    expect(res.body.filename).to.not.equal(null);\n                    expect(res.body.thumbnail).to.not.equal(null);\n                    done();\n                });\n\n\n        });\n\n\n\n    });\n\n});\n\n\n"
  },
  {
    "path": "test/filepRoute.js",
    "content": "\n//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\nprocess.env.ENABLE_ATTACHMENT_RETENTION = \"true\"\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nchai.use(require('chai-string'));\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nlet userService = require('../services/userService');\nlet projectService = require('../services/projectService');\nlet faqService = require('../services/faqService');\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('FileRoute', () => {\n\n    describe('Upload', () => {\n\n        it('post-user-photo', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/users/photo')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'image/jpeg')\n                        .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('Image uploaded successfully');\n                            expect(res.body.filename).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fphoto.jpg`);\n                            expect(res.body.thumbnail).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fthumbnails_200_200-photo.jpg`);\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        it('post-user-photo-already-exists', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/users/photo')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'image/jpeg')\n                        .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('Image uploaded successfully');\n                            expect(res.body.filename).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fphoto.jpg`);\n                            expect(res.body.thumbnail).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fthumbnails_200_200-photo.jpg`);\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/files/users/photo')\n                                .auth(email, pwd)\n                                .set('Content-Type', 'image/jpeg')\n                                .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                                .end((err, res) => {\n                                    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(409);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(false);\n                                    expect(res.body.error).to.equal('Error uploading photo image, file already exists');\n\n                                    done();\n                                })\n\n                        });\n                })\n            })\n        });\n\n        it('post-user-photo-unauthorized', (done) => {\n            let email = \"test-signup-\" + Date.now() + \"@email.com\";\n            let attacker_email = \"attacker-\" + Date.now() + \"@email.com\";\n            let pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                    userService.signup(attacker_email, pwd, \"Test Firstname\", \"Test lastname\").then(function (attackerUser) {\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/files/users/photo')\n                            .auth(attacker_email, pwd)\n                            .set('Content-Type', 'image/jpeg')\n                            .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                            .end((err, res) => {\n            \n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(403);\n                                res.body.should.be.a('object');\n                                expect(res.body.success).to.equal(false);\n                                expect(res.body.msg).to.equal(`you dont belong to the project.`);\n    \n                                done();\n                            });\n                    })\n                })\n            })\n        });\n\n        it('post-chatbot-avatar', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                    faqService.create(savedProject._id, savedUser._id, { name: \"testbot\" }).then(function (savedChatbot) {\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/files/users/photo?bot_id=' + savedChatbot._id)\n                            .auth(email, pwd)\n                            .set('Content-Type', 'image/jpeg')\n                            .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                            .end((err, res) => {\n                                \n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(201);\n                                res.body.should.be.a('object');\n                                expect(res.body.message).to.equal('Image uploaded successfully');\n                                expect(res.body.filename).to.equal(`uploads%2Fusers%2F${savedChatbot._id}%2Fimages%2Fphoto.jpg`);\n                                expect(res.body.thumbnail).to.equal(`uploads%2Fusers%2F${savedChatbot._id}%2Fimages%2Fthumbnails_200_200-photo.jpg`);\n                                done();\n                            });\n                    })\n\n                })\n            })\n        })\n\n        it('post-chatbot-avatar-unauthorized', (done) => {\n            let email = \"test-signup-\" + Date.now() + \"@email.com\";\n            let attacker_email = \"attacker-\" + Date.now() + \"@email.com\";\n            let pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                userService.signup(attacker_email, pwd, \"Test Firstname\", \"Test lastname\").then(function (attackerUser) {\n                    projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                        projectService.create(\"test-attacker-project\", attackerUser._id).then(function (attackerProject) {\n                            faqService.create(savedProject._id, savedUser._id, { name: \"testbot\" }).then(function (savedChatbot) {\n        \n                                chai.request(server)\n                                    .post('/' + attackerProject._id + '/files/users/photo?bot_id=' + savedChatbot._id)\n                                    .auth(attacker_email, pwd)\n                                    .set('Content-Type', 'image/jpeg')\n                                    .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                                    .end((err, res) => {\n                                        \n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n        \n                                        res.should.have.status(401);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(false);\n                                        expect(res.body.error).to.equal(\"You don't belong to the chatbot's project\");\n\n                                        done();\n                                    });\n                            })\n                        })\n                    })\n                })\n            })\n        })\n\n        it('post-chat-pdf', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/chat')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/sample.pdf'), 'sample.pdf')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n        \n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('File uploaded successfully');\n                            expect(res.body.filename).to.not.equal(null);\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        it('post-chat-png', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/chat')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'test-image.png')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('File uploaded successfully');\n                            expect(res.body.filename).to.not.equal(null);\n                            expect(res.body.thumbnail).to.not.equal(null);\n                            done();\n                        });\n                })\n            })\n        });\n\n        it('post-assets-pdf', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/assets')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/sample.pdf'), 'sample.pdf')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n        \n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('File uploaded successfully');\n                            expect(res.body.filename).to.not.equal(null);\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        it('post-assets-png', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                    \n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/assets')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'image/jpeg')\n                        .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'test-image.png')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n        \n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('File uploaded successfully');\n                            expect(res.body.filename).to.not.equal(null);\n                            expect(res.body.thumbnail).to.not.equal(null);\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        it('post-assets-images-retro-compatibility', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n                    \n                    chai.request(server)\n                    .post('/images/users/')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'test-image.png')\n                    // .field('delimiter', ';')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        expect(res.body.filename).to.not.equal(null);\n                        expect(res.body.filename).to.containIgnoreSpaces('test-image.png');\n                        expect(res.body.filename).to.containIgnoreSpaces('users', 'images');\n                        expect(res.body.thumbnail).to.not.equal(null);\n\n                        let filepath = res.body.filename;\n\n                        chai.request(server)\n                            .get('/' + savedProject._id + '/files?path=' + filepath)\n                            .auth(email, pwd)\n                            .end((err, res) => {\n            \n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n            \n                                res.should.have.status(200);\n                                expect(res.body).to.be.instanceof(Buffer)\n                                \n                                done();\n                            });\n                    });\n                })\n            })\n        });\n\n    });\n\n    describe('Security', () => {\n\n        /**\n         * This test verifies that a file with an extension \n         * not present in the whitelist will not be uploaded.\n         */\n        it('post-chat-not-whitelisted-extension', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/chat')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/sample.xyz'), 'sample.xyz')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(403);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.error).to.equal(\"File extension .xyz is not allowed\");\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        /**\n         * This test verifies that an html file whose extension has been renamed to \n         * a whitelisted extension will not be uploaded.\n         */\n        it('post-chat-pdf-attack-html', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/chat')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/fake.pdf'), 'fake.pdf')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(403);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.error).to.equal(\"File content does not match mimetype. Detected: unknown, provided: application/pdf\");\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        /**\n         * This test verifies that a file with an extension \n         * not present in the whitelist will not be uploaded.\n         */\n        it('post-assets-not-whitelisted-extension', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/assets')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/sample.xyz'), 'sample.xyz')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(403);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.error).to.equal(\"File extension .xyz is not allowed\");\n\n                            done();\n                        });\n                })\n            })\n        });\n\n        /**\n         * This test verifies that an html file whose extension has been renamed to \n         * a whitelisted extension will not be uploaded.\n         */\n        it('post-assets-pdf-attack-html', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/assets')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'application/pdf')\n                        .attach('file', fs.readFileSync('./test/fixtures/fake.pdf'), 'fake.pdf')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(403);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.error).to.equal(\"File content does not match mimetype. Detected: unknown, provided: application/pdf\");\n\n                            done();\n                        });\n                })\n            })\n        });\n\n    })\n\n    describe('Delete', () => {\n\n        it('delete-user-photo', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-assets-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/files/users/photo')\n                        .auth(email, pwd)\n                        .set('Content-Type', 'image/jpeg')\n                        .attach('file', fs.readFileSync('./test/fixtures/avatar.jpg'), 'avatar.jpg')\n                        .end((err, res) => {\n        \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(201);\n                            res.body.should.be.a('object');\n                            expect(res.body.message).to.equal('Image uploaded successfully');\n                            expect(res.body.filename).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fphoto.jpg`);\n                            expect(res.body.thumbnail).to.equal(`uploads%2Fusers%2F${savedUser._id}%2Fimages%2Fthumbnails_200_200-photo.jpg`);\n\n                            let filepath = res.body.filename;\n\n                            chai.request(server)\n                                .delete('/' + savedProject._id + '/files?path=' + filepath)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.message).to.equal('File deleted successfully');\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/files?path=' + filepath)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n                                            \n                                            res.should.have.status(404);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(false);\n                                            expect(res.body.error).to.equal('File not found.');\n                                            \n                                            done();\n                                        })\n                                })\n                        });\n                })\n            })\n        });\n\n    })\n\n\n});\n\n\n"
  },
  {
    "path": "test/fixtures/TooManykbUrlsList.txt",
    "content": "https://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4\nhttps://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4"
  },
  {
    "path": "test/fixtures/example-faqs.csv",
    "content": "﻿Question 1; Answer 1; question1"
  },
  {
    "path": "test/fixtures/example-json-intents.txt",
    "content": "{\"intents\":[{\"webhook_enabled\":false,\"enabled\":true,\"question\":\"\\\\start\",\"answer\":\"Hello\",\"intent_display_name\":\"start\",\"language\":\"en\"},{\"webhook_enabled\":false,\"enabled\":true,\"question\":\"defaultFallback\",\"answer\":\"I can not provide an adequate answer. Write a new question or talk to a human agent.\\n* Back to start tdAction:start\\n* See the docs https://docs.tiledesk.com/\\n* 👨🏻‍🦰 I want an agent\",\"intent_display_name\":\"defaultFallback\",\"language\":\"en\"}]}"
  },
  {
    "path": "test/fixtures/example-json-multiple-operation-mock.js",
    "content": "// const json_multiple_operation = {\n//   \"id_faq_kb\": \"6543c2357be357002c682908\",\n//   \"operations\": [\n//     {\n//       \"type\": \"delete\",\n//       \"intent\": {\n//         \"webhook_enabled\": false,\n//         \"enabled\": true,\n//         \"topic\": \"default\",\n//         \"status\": \"live\",\n//         \"actions\": [\n//           {\n//             \"_tdActionTitle\": \"\",\n//             \"_tdActionId\": \"846d1b3c-3f2c-4a53-bf07-0b0b5cc7e3dd\",\n//             \"_tdActionType\": \"close\"\n//           }\n//         ],\n//         \"_id\": \"655b46ebbaddb8002c5a889e\",\n//         \"id_faq_kb\": \"6543c2357be357002c682908\",\n//         \"id_project\": \"64a6e63d4152c9002c927891\",\n//         \"language\": \"en\",\n//         \"intent_display_name\": \"untitled_block_4\",\n//         \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//         \"intent_id\": \"7c25c967-3162-451c-a7de-b240d8c8265b\",\n//         \"attributes\": {\n//           \"position\": {\n//             \"x\": 2331,\n//             \"y\": -611.5\n//           },\n//           \"nextBlockAction\": {\n//             \"_tdActionId\": \"9f12ef32-ca5f-4bc9-8a40-76a179e9ebcb\",\n//             \"_tdActionType\": \"intent\",\n//             \"intentName\": \"\"\n//           }\n//         },\n//         \"createdAt\": \"2023-11-20T11:45:47.448Z\",\n//         \"updatedAt\": \"2023-11-20T11:45:47.448Z\",\n//         \"faq_kb\": [\n//           {\n//             \"webhook_enabled\": false,\n//             \"type\": \"tilebot\",\n//             \"language\": \"en\",\n//             \"public\": false,\n//             \"certified\": false,\n//             \"intentsEngine\": \"none\",\n//             \"tags\": [],\n//             \"score\": 0,\n//             \"trained\": true,\n//             \"certifiedTags\": [],\n//             \"_id\": \"6543c2357be357002c682908\",\n//             \"name\": \"02nov16:37\",\n//             \"id_project\": \"64a6e63d4152c9002c927891\",\n//             \"trashed\": false,\n//             \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//             \"createdAt\": \"2023-11-02T15:37:25.364Z\",\n//             \"updatedAt\": \"2023-11-02T15:37:25.400Z\",\n//             \"url\": \"https://tiledesk-server-pre.herokuapp.com/modules/tilebot/ext/6543c2357be357002c682908\"\n//           }\n//         ],\n//         \"id\": \"655b46ebbaddb8002c5a889e\"\n//       }\n//     },\n//     {\n//       \"type\": \"put\",\n//       \"intent\": {\n//         \"webhook_enabled\": false,\n//         \"enabled\": true,\n//         \"topic\": \"default\",\n//         \"status\": \"live\",\n//         \"actions\": [\n//           {\n//             \"_tdActionTitle\": \"\",\n//             \"_tdActionId\": \"bee43d8a-03d7-4b51-a22a-83c55a13eadf\",\n//             \"_tdActionType\": \"close\"\n//           }\n//         ],\n//         \"_id\": \"65579a7fbcee1b003c6dd6fb\",\n//         \"id_faq_kb\": \"6543c2357be357002c682908\",\n//         \"id_project\": \"64a6e63d4152c9002c927891\",\n//         \"language\": \"en\",\n//         \"intent_display_name\": \"untitled_block_2\",\n//         \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//         \"intent_id\": \"ede5cf7f-ce09-49d2-921c-55a9faf099d8\",\n//         \"attributes\": {\n//           \"position\": {\n//             \"x\": 1904,\n//             \"y\": -746\n//           },\n//           \"nextBlockAction\": {\n//             \"_tdActionId\": \"481701dd-661d-45ee-97ec-7034720eb235\",\n//             \"_tdActionType\": \"intent\",\n//             \"intentName\": \"\"\n//           }\n//         },\n//         \"createdAt\": \"2023-11-17T16:53:19.856Z\",\n//         \"updatedAt\": \"2023-11-20T14:56:51.089Z\",\n//         \"answer\": \"\",\n//         \"question\": \"\",\n//         \"faq_kb\": [\n//           {\n//             \"webhook_enabled\": false,\n//             \"type\": \"tilebot\",\n//             \"language\": \"en\",\n//             \"public\": false,\n//             \"certified\": false,\n//             \"intentsEngine\": \"none\",\n//             \"tags\": [],\n//             \"score\": 0,\n//             \"trained\": true,\n//             \"certifiedTags\": [],\n//             \"_id\": \"6543c2357be357002c682908\",\n//             \"name\": \"02nov16:37\",\n//             \"id_project\": \"64a6e63d4152c9002c927891\",\n//             \"trashed\": false,\n//             \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//             \"createdAt\": \"2023-11-02T15:37:25.364Z\",\n//             \"updatedAt\": \"2023-11-02T15:37:25.400Z\",\n//             \"url\": \"https://tiledesk-server-pre.herokuapp.com/modules/tilebot/ext/6543c2357be357002c682908\"\n//           }\n//         ],\n//         \"id\": \"65579a7fbcee1b003c6dd6fb\"\n//       }\n//     },\n//     {\n//       \"type\": \"put\",\n//       \"intent\": {\n//         \"webhook_enabled\": false,\n//         \"enabled\": true,\n//         \"topic\": \"default\",\n//         \"status\": \"live\",\n//         \"actions\": [\n//           {\n//             \"_tdActionTitle\": \"\",\n//             \"_tdActionId\": \"34f6ae25-829b-4d9c-8899-5ba85a53861b\",\n//             \"_tdActionType\": \"reply\",\n//             \"attributes\": {\n//               \"disableInputMessage\": false,\n//               \"commands\": [\n//                 {\n//                   \"type\": \"wait\",\n//                   \"time\": 500\n//                 },\n//                 {\n//                   \"type\": \"message\",\n//                   \"message\": {\n//                     \"type\": \"text\",\n//                     \"text\": \"A chat message will be sent to the visitor ...\",\n//                     \"attributes\": {\n//                       \"attachment\": {\n//                         \"type\": \"template\",\n//                         \"buttons\": [\n//                           {\n//                             \"uid\": \"5d18e074bded4b4b8ff2724cbfd44d04\",\n//                             \"type\": \"action\",\n//                             \"value\": \"Button\",\n//                             \"link\": \"\",\n//                             \"target\": \"blank\",\n//                             \"action\": \"\",\n//                             \"attributes\": \"\",\n//                             \"show_echo\": true\n//                           }\n//                         ]\n//                       }\n//                     }\n//                   }\n//                 }\n//               ]\n//             }\n//           }\n//         ],\n//         \"_id\": \"655b44b3baddb8002c5a8708\",\n//         \"id_faq_kb\": \"6543c2357be357002c682908\",\n//         \"id_project\": \"64a6e63d4152c9002c927891\",\n//         \"language\": \"en\",\n//         \"intent_display_name\": \"untitled_block_3\",\n//         \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//         \"intent_id\": \"06e171e6-614e-4092-b949-b04e21c674f0\",\n//         \"attributes\": {\n//           \"position\": {\n//             \"x\": 1851,\n//             \"y\": -435\n//           },\n//           \"nextBlockAction\": {\n//             \"_tdActionId\": \"9cfa41bf-ef98-4bce-b37f-18eb52e17b3a\",\n//             \"_tdActionType\": \"intent\",\n//             \"intentName\": \"\"\n//           }\n//         },\n//         \"createdAt\": \"2023-11-20T11:36:19.054Z\",\n//         \"updatedAt\": \"2023-11-20T14:42:40.825Z\",\n//         \"answer\": \"\",\n//         \"question\": \"\",\n//         \"faq_kb\": [\n//           {\n//             \"webhook_enabled\": false,\n//             \"type\": \"tilebot\",\n//             \"language\": \"en\",\n//             \"public\": false,\n//             \"certified\": false,\n//             \"intentsEngine\": \"none\",\n//             \"tags\": [],\n//             \"score\": 0,\n//             \"trained\": true,\n//             \"certifiedTags\": [],\n//             \"_id\": \"6543c2357be357002c682908\",\n//             \"name\": \"02nov16:37\",\n//             \"id_project\": \"64a6e63d4152c9002c927891\",\n//             \"trashed\": false,\n//             \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n//             \"createdAt\": \"2023-11-02T15:37:25.364Z\",\n//             \"updatedAt\": \"2023-11-02T15:37:25.400Z\",\n//             \"url\": \"https://tiledesk-server-pre.herokuapp.com/modules/tilebot/ext/6543c2357be357002c682908\"\n//           }\n//         ],\n//         \"id\": \"655b44b3baddb8002c5a8708\"\n//       }\n//     }\n//   ]\n// }\n\nconst json_multiple_operation = {\n  \"id_faq_kb\": \"6543c2357be357002c682908\",\n  \"operations\": [\n    {\n      \"type\": \"delete\",\n      \"intent\": {\n        \"webhook_enabled\": false,\n        \"enabled\": true,\n        \"topic\": \"default\",\n        \"status\": \"live\",\n        \"actions\": [\n          {\n            \"_tdActionTitle\": \"\",\n            \"_tdActionId\": \"846d1b3c-3f2c-4a53-bf07-0b0b5cc7e3dd\",\n            \"_tdActionType\": \"close\"\n          }\n        ],\n        \"_id\": \"655b46ebbaddb8002c5a889e\",\n        \"id_faq_kb\": \"6543c2357be357002c682908\",\n        \"id_project\": \"64a6e63d4152c9002c927891\",\n        \"language\": \"en\",\n        \"intent_display_name\": \"untitled_block_4\",\n        \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n        \"intent_id\": \"7c25c967-3162-451c-a7de-b240d8c8265b\",\n        \"attributes\": {\n          \"position\": {\n            \"x\": 2331,\n            \"y\": -611.5\n          },\n          \"nextBlockAction\": {\n            \"_tdActionId\": \"9f12ef32-ca5f-4bc9-8a40-76a179e9ebcb\",\n            \"_tdActionType\": \"intent\",\n            \"intentName\": \"\"\n          }\n        },\n        \"createdAt\": \"2023-11-20T11:45:47.448Z\",\n        \"updatedAt\": \"2023-11-20T11:45:47.448Z\",\n        \"faq_kb\": [\n          {\n            \"webhook_enabled\": false,\n            \"type\": \"tilebot\",\n            \"language\": \"en\",\n            \"public\": false,\n            \"certified\": false,\n            \"intentsEngine\": \"none\",\n            \"tags\": [],\n            \"score\": 0,\n            \"trained\": true,\n            \"certifiedTags\": [],\n            \"_id\": \"6543c2357be357002c682908\",\n            \"name\": \"02nov16:37\",\n            \"id_project\": \"64a6e63d4152c9002c927891\",\n            \"trashed\": false,\n            \"createdBy\": \"5ab0f3fa57066e0014bfd71e\",\n            \"createdAt\": \"2023-11-02T15:37:25.364Z\",\n            \"updatedAt\": \"2023-11-02T15:37:25.400Z\",\n            \"url\": \"https://tiledesk-server-pre.herokuapp.com/modules/tilebot/ext/6543c2357be357002c682908\"\n          }\n        ],\n        \"id\": \"655b46ebbaddb8002c5a889e\"\n      }\n    }\n  ]\n}\n\n\nmodule.exports = { json_multiple_operation };\n"
  },
  {
    "path": "test/fixtures/example-json-rules.txt",
    "content": "{\n    \"webhook_enabled\" : false,\n    \"type\" : \"tilebot\",\n    \"secret\" : \"0c9021c8-3b62-47ce-98f2-cbc7b47639d1\",\n    \"language\" : \"en\",\n    \"public\" : true,\n    \"certified\" : false,\n    \"intentsEngine\" : \"none\",\n    \"tags\" : [ \n        \"quiz\", \n        \"onboarding\", \n        \"whatsapp\", \n        \"chatbot\", \n        \"english\", \n        \"buttons\"\n    ],\n    \"score\" : 0,\n    \"name\" : \"example bot\",\n    \"slug\": \"my-example-bot\",\n    \"id_project\" : \"64218dfecdb804001380b9ba\",\n    \"trashed\" : false,\n    \"createdBy\" : \"63a05d755f117f0013541383\",\n    \"attributes\" : {\n        \"variables\" : {\n            \"total\" : \"total\",\n            \"score\" : \"score\",\n            \"userFullname\" : \"userFullname\"\n        },\n        \"rules\" : [ \n            {\n                \"uid\" : \"6f7f1206-0433-4ea5-a1f5-d688334fe147\",\n                \"name\" : \"go2\",\n                \"description\" : \"\",\n                \"when\" : {\n                    \"regexOption\" : \"any\",\n                    \"text\" : \"\",\n                    \"urlMatches\" : \"^.*$\",\n                    \"triggerEvery\" : -1\n                },\n                \"do\" : [ \n                    {\n                        \"message\" : {\n                            \"text\" : \"/proactive2\",\n                            \"participants\" : [ \n                                \"bot_64232b3f2e14140013a9a623\"\n                            ],\n                            \"attributes\" : {\n                                \"subtype\" : \"info\"\n                            }\n                        }\n                    }\n                ]\n            }, \n            {\n                \"uid\" : \"4aa55b98-8450-4f59-b82e-06b794ed3edf\",\n                \"name\" : \"go\",\n                \"description\" : \"\",\n                \"when\" : {\n                    \"regexOption\" : \"ends\",\n                    \"text\" : \"ppp\",\n                    \"urlMatches\" : \"^.*(ppp)$\",\n                    \"triggerEvery\" : -1\n                },\n                \"do\" : [ \n                    {\n                        \"message\" : {\n                            \"text\" : \"/proactive\",\n                            \"participants\" : [ \n                                \"bot_64232b3f2e14140013a9a623\"\n                            ],\n                            \"attributes\" : {\n                                \"subtype\" : \"info\"\n                            }\n                        }\n                    }\n                ]\n            }\n        ]\n    },\n    \"__v\" : 0,\n    \"url\" : \"http://localhost:3000/modules/tilebot/ext/6425b5c495fcdb0012beb9b9\",\n    \"description\" : \"A simple Javascript test submitted with a Chatbot 🤓\\n\\nYou can use this test to create and submit a simple test to your audience. This test makes wide use of \\\"attributes\\\" and \\\"conditions\\\" (aka the \\\"flow\\\"). We also created a rule to proactively engage your visitors to open and complete the test. We also used \\\"forms\\\" to ask your visitor's data (please mind that privacy concerns are left to you).\\n\\nIt also works on Whatsapp!\\n\\nFeel free to import and modify this test as you prefer.\\n\\nLet us know if you have some questions about this template writing a message to andrea@tiledesk.com\"\n}"
  },
  {
    "path": "test/fixtures/example-json.txt",
    "content": "{\"webhook_enabled\":false,\"language\":\"en\",\"name\":\"examplebot\",\"intentsEngine\":\"tiledesk-ai\",\"attributes\" : {\"variables\" : {\"total\" : \"total\",\"score\" : \"score\",\"userFullname\" : \"userFullname\"},\"rules\" : [{\"uid\" : \"6f7f1206-0433-4ea5-a1f5-d688334fe147\",\"name\" : \"go2\",\"description\" : \"\",\"when\" : {\"regexOption\" : \"any\",\"text\" : \"\",\"urlMatches\" : \"^.*$\",\"triggerEvery\" : -1},\"do\" : [ {\"message\" : {\"text\" : \"/proactive2\",\"participants\" : [ \"bot_64232b3f2e14140013a9a623\"],\"attributes\" : {\"subtype\" : \"info\"}}}]}, {\"uid\" : \"4aa55b98-8450-4f59-b82e-06b794ed3edf\",\"name\" : \"go\",\"description\" : \"\",\"when\" : {\"regexOption\" : \"ends\",\"text\" : \"ppp\",\"urlMatches\" : \"^.*(ppp)$\",\"triggerEvery\" : -1},\"do\":[{\"message\" : {\"text\" : \"/proactive\",\"participants\" : [\"bot_64232b3f2e14140013a9a623\"],\"attributes\" : {\"subtype\" : \"info\"}}}]}]},\"intents\":[{\"webhook_enabled\":false,\"enabled\":true,\"question\":\"\\\\start\",\"answer\":\"Hi\",\"intent_display_name\":\"start\",\"language\":\"en\"},{\"webhook_enabled\":false,\"enabled\":true,\"question\":\"question1\",\"answer\":\"question1\",\"intent_display_name\":\"question1\",\"language\":\"en\"},{\"webhook_enabled\":false,\"enabled\":true,\"question\":\"defaultFallback\",\"answer\":\"I can not provide an adequate answer. Write a new question or talk to a human agent.\\n* Back to start tdAction:start\\n* See the docs https://docs.tiledesk.com/\\n* 👨🏻‍🦰 I want an agent\",\"intent_display_name\":\"defaultFallback\",\"language\":\"en\"}]}"
  },
  {
    "path": "test/fixtures/example-kb-faqs.csv",
    "content": "﻿Question 1;Answer 1\nQuestion 2;Answer 2"
  },
  {
    "path": "test/fixtures/example-webhook-json.txt",
    "content": "{\"webhook_enabled\":false,\"language\":\"en\",\"name\":\"Flow 1\",\"slug\":\"flow-1\",\"type\":\"tilebot\",\"subtype\":\"webhook\",\"intents\":[{\"webhook_enabled\":false,\"enabled\":true,\"actions\":[{\"_tdActionType\":\"intent\",\"intentName\":\"#5da40c2c-8711-47b0-98c7-744c253fa255\",\"_tdActionId\":\"5f54ccf7328a4f6e9ac2e6cf06374595\"}],\"intent_id\":\"93882faf-e0b7-4fab-8816-70ecea96d70d\",\"question\":\"\",\"intent_display_name\":\"webhook\",\"language\":\"en\",\"attributes\":{\"position\":{\"x\":172,\"y\":384},\"readonly\":true,\"nextBlockAction\":{\"_tdActionTitle\":\"\",\"_tdActionId\":\"bdc317fa-9372-49ac-8373-b922210292e9\",\"_tdActionType\":\"intent\"},\"connectors\":{}},\"agents_available\":false},{\"webhook_enabled\":false,\"enabled\":true,\"actions\":[{\"_tdActionTitle\":\"\",\"_tdActionId\":\"0eeee924-7b2b-41a4-8c10-731d1c28ee6c\",\"_tdActionType\":\"gpt_task\",\"max_tokens\":256,\"temperature\":0.7,\"model\":\"gpt-4o\",\"assignReplyTo\":\"gpt_reply\",\"preview\":[],\"formatType\":\"none\",\"question\":\"Quanto fa 2+2? Rispondi solo con il risultato.\",\"context\":\"\",\"trueIntent\":\"#f38844ea-c490-40ee-b32a-6a59e77410fc\",\"falseIntent\":null}],\"language\":\"en\",\"intent_display_name\":\"untitled_block_1\",\"intent_id\":\"5da40c2c-8711-47b0-98c7-744c253fa255\",\"agents_available\":false,\"attributes\":{\"position\":{\"x\":560,\"y\":281},\"nextBlockAction\":{\"_tdActionId\":\"8f98f384-adb2-4b6b-a31a-de5f751cd658\",\"_tdActionType\":\"intent\",\"intentName\":\"\"},\"connectors\":{},\"color\":\"156,163,205\",\"readonly\":false}},{\"webhook_enabled\":false,\"enabled\":true,\"actions\":[{\"_tdActionTitle\":\"\",\"_tdActionId\":\"f046856f-afbd-47b3-9f23-f6d109ce25f4\",\"payload\":\"{\\n\\\"success\\\": true,\\n\\\"result\\\": {{gpt_reply}}\\n}\",\"headersString\":{\"Content-Type\":\"*/*\",\"Cache-Control\":\"no-cache\",\"User-Agent\":\"TiledeskBotRuntime\",\"Accept\":\"*/*\"},\"bodyType\":\"json\",\"assignTo\":\"\",\"_tdActionType\":\"web_response\",\"status\":\"200\"}],\"language\":\"en\",\"intent_display_name\":\"untitled_block_2\",\"intent_id\":\"f38844ea-c490-40ee-b32a-6a59e77410fc\",\"agents_available\":false,\"attributes\":{\"position\":{\"x\":1001,\"y\":296},\"nextBlockAction\":{\"_tdActionId\":\"db01c4fd-f14a-413a-8d4d-12a53da49267\",\"_tdActionType\":\"intent\",\"intentName\":\"\"},\"color\":\"156,163,205\",\"readonly\":false}}]}"
  },
  {
    "path": "test/fixtures/example.json",
    "content": "{\n    \"webhook_enabled\": false,\n    \"language\": \"en\",\n    \"name\": \"examplebot\",\n    \"intents\": [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"\\\\start\",\n            \"answer\": \"Hi\",\n            \"intent_display_name\": \"start\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"question1\",\n            \"answer\": \"question1\",\n            \"intent_display_name\": \"question1\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"defaultFallback\",\n            \"answer\": \"I can not provide an adequate answer. Write a new question or talk to a human agent.\\n* Back to start tdAction:start\\n* See the docs https://docs.tiledesk.com/\\n* 👨🏻‍🦰 I want an agent\",\n            \"intent_display_name\": \"defaultFallback\",\n            \"language\": \"en\"\n        }\n    ]\n}"
  },
  {
    "path": "test/fixtures/exported_namespace.json",
    "content": "{\n    \"name\": \"export2\",\n    \"preview_settings\": {\n        \"model\": \"gpt-4o\",\n        \"max_tokens\": 256,\n        \"temperature\": 0.7,\n        \"top_k\": 4,\n        \"context\": null\n    },\n    \"contents\": [\n        {\n            \"_id\": \"6835bee22c061f7021f4f7c0\",\n            \"id_project\": \"62c3f10152dc7400352bab0d\",\n            \"source\": \"Example content\",\n            \"type\": \"text\",\n            \"__v\": 0,\n            \"content\": \"This is an example content.\",\n            \"createdAt\": \"2025-05-27T13:32:18.078Z\",\n            \"name\": \"Example content\",\n            \"namespace\": \"6835be87d0a352002d806f73\",\n            \"status\": -1,\n            \"updatedAt\": \"2025-05-27T13:32:18.078Z\",\n            \"tags\": [\"tag1\", \"tag2\"]\n        },\n        {\n            \"_id\": \"6835bed02c061f7021f46e99\",\n            \"id_project\": \"62c3f10152dc7400352bab0d\",\n            \"source\": \"custom question\",\n            \"type\": \"faq\",\n            \"__v\": 0,\n            \"content\": \"custom question\\nthis is the answer!\",\n            \"createdAt\": \"2025-05-27T13:32:00.574Z\",\n            \"name\": \"custom question\",\n            \"namespace\": \"6835be87d0a352002d806f73\",\n            \"status\": -1,\n            \"updatedAt\": \"2025-05-27T13:32:00.574Z\"\n        },\n        {\n            \"_id\": \"6835bebc2c061f7021f3daa4\",\n            \"id_project\": \"62c3f10152dc7400352bab0d\",\n            \"namespace\": \"6835be87d0a352002d806f73\",\n            \"source\": \"https://gethelp.tiledesk.com/articles/configure-a-whatsapp-business-account/\",\n            \"type\": \"url\",\n            \"content\": \"\",\n            \"createdAt\": \"2025-05-27T13:31:40.958Z\",\n            \"name\": \"https://gethelp.tiledesk.com/articles/configure-a-whatsapp-business-account/\",\n            \"refresh_rate\": \"never\",\n            \"scrape_options\": {\n                \"tags_to_extract\": [\n                    \"article\"\n                ],\n                \"unwanted_tags\": [\n                    \"h1\",\n                    \"h2\"\n                ],\n                \"unwanted_classnames\": []\n            },\n            \"scrape_type\": 4,\n            \"status\": 400,\n            \"updatedAt\": \"2025-05-27T13:31:41.022Z\"\n        }\n    ]\n}"
  },
  {
    "path": "test/fixtures/kbUrlsList.txt",
    "content": "https://gethelp.tiledesk.com/articles/article1\nhttps://gethelp.tiledesk.com/articles/article2\nhttps://gethelp.tiledesk.com/articles/article3\nhttps://gethelp.tiledesk.com/articles/article4"
  },
  {
    "path": "test/fixtures/sample.xyz",
    "content": ""
  },
  {
    "path": "test/imageRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nchai.use(require('chai-string'));\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nvar userService = require('../services/userService');\nconst projectService = require('../services/projectService');\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('ImagesRoute', () => {\n\n    describe('/upload', () => {\n\n        it('upload-user', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .post('/images/users/')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'test-image.png')\n                    // .field('delimiter', ';')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        expect(res.body.filename).to.not.equal(null);\n                        expect(res.body.filename).to.containIgnoreSpaces('test-image.png');\n                        expect(res.body.filename).to.containIgnoreSpaces('users', 'images');\n                        expect(res.body.thumbnail).to.not.equal(null);\n                        done();\n                    });\n\n            });\n        });\n\n\n        // mocha test/imageRoute.js  --grep 'upload-user-folder'\n        it('upload-user-folder', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .put('/images/users')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                    // .field('folder', 'myfolder')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        expect(res.body.filename).to.not.equal(null);\n                        expect(res.body.filename).to.containIgnoreSpaces('profile.png');\n                        expect(res.body.filename).to.containIgnoreSpaces('users', 'images');\n                        // assert(res.body.filename.indexOf()).to.have.string('');                             \n                        // assert.equal(res.body.filename.indexOf('myfilder'), 1);                                           \n                        expect(res.body.thumbnail).to.not.equal(null);\n\n                        //check duplicate\n                        chai.request(server)\n                            .put('/images/users')\n                            .auth(email, pwd)\n                            .set('Content-Type', 'image/jpeg')\n                            .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                            // .field('folder', 'myfolder')            \n                            .end((err, res) => {\n                                res.should.have.status(409);\n                                done();\n                            });\n                    });\n            });\n        });\n\n\n        // mocha test/imageRoute.js  --grep 'upload-avatar'\n        it('upload-avatar', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .put('/images/users/photo')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                    // .field('folder', 'myfolder')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        // expect(res.body.filename).to.not.equal(\"photo.jpg\");  \n                        expect(res.body.filename).to.containIgnoreSpaces('photo.jpg');\n                        expect(res.body.filename).to.containIgnoreSpaces('users', 'images');\n                        // assert(res.body.filename.indexOf()).to.have.string('');                             \n                        // assert.equal(res.body.filename.indexOf('myfilder'), 1);                                           \n                        expect(res.body.thumbnail).to.not.equal(null);\n\n                        //check duplicate\n                        chai.request(server)\n                            .put('/images/users/photo')\n                            .auth(email, pwd)\n                            .set('Content-Type', 'image/jpeg')\n                            .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                            // .field('folder', 'myfolder')            \n                            .end((err, res) => {\n\n                                res.should.have.status(409);\n                                done();\n                            });\n                    });\n            });\n        }).timeout(5000);\n\n\n        // mocha test/imageRoute.js  --grep 'upload-avatar-force'\n        it('upload-avatar-force', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .put('/images/users/photo?force=true')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                    // .field('folder', 'myfolder')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        // expect(res.body.filename).to.not.equal(\"photo.jpg\");  \n                        expect(res.body.filename).to.containIgnoreSpaces('photo.jpg');\n                        expect(res.body.filename).to.containIgnoreSpaces('users', 'images');\n                        // assert(res.body.filename.indexOf()).to.have.string('');                             \n                        // assert.equal(res.body.filename.indexOf('myfilder'), 1);                                           \n                        expect(res.body.thumbnail).to.not.equal(null);\n\n                        //check duplicate\n                        chai.request(server)\n                            .put('/images/users/photo?force=true')\n                            .auth(email, pwd)\n                            .set('Content-Type', 'image/jpeg')\n                            .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                            // .field('folder', 'myfolder')            \n                            .end((err, res) => {\n                                res.should.have.status(201);\n                                done();\n                            });\n                    });\n            });\n        });\n\n\n        // mocha test/imageRoute.js  --grep 'upload-avatar-another-user'\n        it('upload-avatar-another-user', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/faq_kb')\n                        .auth(email, pwd)\n                        .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: 'en' })\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body); }\n\n                            //var bot = \"bot_\" + Date.now();\n                            let bot_id = res.body._id;\n\n                            chai.request(server)\n                                .put('/images/users/photo?bot_id=' + bot_id)\n                                .auth(email, pwd)\n                                .set('Content-Type', 'image/jpeg')\n                                .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                                // .field('folder', 'myfolder')            \n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(201);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.message).to.equal('Image uploded successfully');\n                                    expect(res.body.filename).to.not.equal(\"photo.jpg\");\n                                    // assert(res.body.filename.indexOf()).to.have.string('');                             \n                                    // assert.equal(res.body.filename.indexOf('myfilder'), 1);                                           \n                                    expect(res.body.thumbnail).to.not.equal(null);\n                                    // expect(res.body.filename).to.include.keys(bot);\n                                    expect(res.body.filename).to.containIgnoreSpaces(bot_id);\n\n                                    //check duplicate\n                                    chai.request(server)\n                                        .put('/images/users/photo?bot_id=' + bot_id)\n                                        .auth(email, pwd)\n                                        .set('Content-Type', 'image/jpeg')\n                                        .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                                        // .field('folder', 'myfolder')            \n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body: \", res.body); }\n                                            \n                                            res.should.have.status(409);\n                                            \n                                            done();\n                                        });\n                                });\n\n                        })\n                });\n            })\n        });\n\n\n        // mocha test/imageRoute.js  --grep 'delete-user-folder'\n        it('delete-user-folder', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n                chai.request(server)\n                    .put('/images/users')\n                    .auth(email, pwd)\n                    .set('Content-Type', 'image/jpeg')\n                    .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'profile.png')\n                    // .field('folder', 'myfolder')            \n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(201);\n                        res.body.should.be.a('object');\n                        expect(res.body.message).to.equal('Image uploded successfully');\n                        expect(res.body.filename).to.not.equal(null);\n                        // assert(res.body.filename.indexOf()).to.have.string('');                             \n                        // assert.equal(res.body.filename.indexOf('myfilder'), 1);                                           \n                        expect(res.body.thumbnail).to.not.equal(null);\n\n                        chai.request(server)\n                            .delete('/images/users?path=' + res.body.filename)\n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                expect(res.body.message).to.equal('Image deleted successfully');\n                                done();\n                            });\n                    });\n            });\n        });\n\n\n        // mocha test/imageRoute.js  --grep 'upload-public'\n        it('upload-public', (done) => {\n\n            chai.request(server)\n                .post('/images/public/')\n                .set('Content-Type', 'image/jpeg')\n                .attach('file', fs.readFileSync('./test/fixtures/test-image.png'), 'test-image.png')\n                // .field('delimiter', ';')            \n                .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n\n                    res.should.have.status(201);\n                    res.body.should.be.a('object');\n                    expect(res.body.message).to.equal('Image uploded successfully');\n                    expect(res.body.filename).to.not.equal(null);\n                    expect(res.body.thumbnail).to.not.equal(null);\n\n                    done();\n                });\n        });\n    });\n\n});\n\n\n"
  },
  {
    "path": "test/jwtRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar jwt = require('jsonwebtoken');\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('JWTRoute', () => {\n\n  describe('/decode', () => {\n\n    it('decodeWithIatExp', (done) => {\n\n      var email = \"test-signup-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"test-join-member\", savedUser._id).then(function (savedProject) {\n          chai.request(server)\n            .post('/' + savedProject._id + '/keys/generate')\n            .auth(email, pwd)\n            .send()\n            .end((err, res) => {\n\n              if (err) { console.error(\"err: \", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              expect(res.body.jwtSecret).to.not.equal(null);\n\n              chai.request(server)\n                .post('/' + savedProject._id + '/jwt/generatetestjwt')\n                .auth(email, pwd)\n                .send({ \"name\": \"andrea\", \"surname\": \"leo\" })\n                .end((err, res) => {\n\n                  if (err) { console.error(\"err: \", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.token).to.not.equal(null);\n\n                  var jwtToken = res.body.token;\n                  if (log) { console.log(\"jwtToken\", jwtToken); }\n                  \n                  chai.request(server)\n                    .post('/' + savedProject._id + '/jwt/decode')\n                    .set('Authorization', jwtToken)\n                    .send()\n                    .end((err, res) => {\n\n                      res.should.have.status(200);\n                      res.body.should.be.a('object');\n                      \n                      expect(res.body.name).to.not.equal(\"andrea\");\n                      expect(res.body.surname).to.not.equal(\"leo\");\n                      expect(res.body.iat).to.not.equal(null);\n                      expect(res.body.exp).to.not.equal(null);\n\n                      done();\n                    });\n                });\n            });\n        });\n      });\n    }).timeout(20000);\n\n\n    it('decodeWithIatNoExp', (done) => {\n\n      var email = \"test-signup-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"test-join-member\", savedUser._id).then(function (savedProject) {\n          chai.request(server)\n            .post('/' + savedProject._id + '/keys/generate')\n            .auth(email, pwd)\n            .send()\n            .end((err, res) => {\n\n              if (err) { console.error(\"err: \", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              expect(res.body.jwtSecret).to.not.equal(null);\n\n              var jwtToken = jwt.sign({ \"name\": \"andrea\", \"surname\": \"leo\" }, res.body.jwtSecret);\n              if (log) { console.log(\"jwtToken\", jwtToken); }\n\n              chai.request(server)\n                .post('/' + savedProject._id + '/jwt/decode')\n                .set('Authorization', 'JWT ' + jwtToken)\n                .send()\n                .end((err, res) => {\n\n                  if (err) { console.error(\"err: \", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n\n                  res.should.have.status(401);\n\n                  done();\n                });\n            });\n        });\n      });\n    }).timeout(20000);\n\n\n    it('decodeWithIatTooHightExp', (done) => {\n\n      var email = \"test-signup-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"test-join-member\", savedUser._id).then(function (savedProject) {\n          chai.request(server)\n            .post('/' + savedProject._id + '/keys/generate')\n            .auth(email, pwd)\n            .send()\n            .end((err, res) => {\n\n              if (err) { console.error(\"err: \", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              expect(res.body.jwtSecret).to.not.equal(null);\n\n              var jwtToken = jwt.sign({ \"name\": \"andrea\", \"surname\": \"leo\" }, res.body.jwtSecret, { expiresIn: 800 });\n              if (log) { console.log(\"jwtToken\", jwtToken); }\n\n              chai.request(server)\n                .post('/' + savedProject._id + '/jwt/decode')\n                .set('Authorization', 'JWT ' + jwtToken)\n                .send()\n                .end((err, res) => {\n\n                  if (err) { console.error(\"err: \", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n\n                  res.should.have.status(401);\n\n                  done();\n                });\n            });\n        });\n      });\n    }).timeout(20000);\n\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/kbRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.GPTKEY = \"fakegptkey\";\nprocess.env.LOG_LEVEL = 'critical'\nprocess.env.KB_WEBHOOK_TOKEN = \"testtoken\"\nprocess.env.PINECONE_INDEX = \"test-index\";\nprocess.env.PINECONE_TYPE = \"serverless\";\nprocess.env.PINECONE_INDEX_HYBRID = \"test-index-hybrid\";\nprocess.env.PINECONE_TYPE_HYBRID = \"serverless\";\nprocess.env.ADMIN_EMAIL = \"admin@tiledesk.com\";\n\nvar userService = require('../services/userService');\nvar projectService = require('../services/projectService');\nvar faqService = require('../services/faqService');\n\nlet log = false;\n\nvar config = require('../config/global');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\nconst mongoose = require('mongoose');\nconst nock = require('nock');\nconst faq = require('../models/faq');\n\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nlet custom_profile_sample = { \n    name: \"Custom\",\n    type: \"payment\",\n    subStart: new Date(),\n    subEnd: new Date(new Date().setFullYear(new Date().getFullYear() + 1)),\n    customization: { hybrid: true } \n}\n\nmongoose.connect(config.databasetest);\n\nchai.use(chaiHttp);\n\ndescribe('KbRoute', () => {\n\n    describe('Namespaces', () => {\n\n        /**\n         * Get all namespaces of a project.\n         * If there isn't namespaces for a project_id, the default namespace is created and returned.\n         */\n        it('get-all-namespaces', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n                            expect(res.body.length).to.equal(1);\n                            should.not.exist(res.body[0]._id);\n                            expect(res.body[0].id).to.equal(savedProject._id.toString());\n                            expect(res.body[0].name).to.equal(\"Default\");\n                            should.exist(res.body[0].engine);\n                            expect(res.body[0].engine.name).to.equal('pinecone');\n                            expect(res.body[0].engine.type).to.equal('serverless');\n                            expect(res.body[0].engine.vector_size).to.equal(1536);\n                            expect(res.body[0].engine.index_name).to.equal('test-index');\n                            should.exist(res.body[0].embedding);\n                            expect(res.body[0].embedding.provider).to.equal('openai')\n                            expect(res.body[0].embedding.name).to.equal('text-embedding-ada-002')\n                            expect(res.body[0].embedding.dimension).to.equal(1536)\n\n                            done();\n                        })\n\n                });\n            });\n\n        })\n\n        it('create-namespaces-with-engine-similarity', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kb/namespace')\n                        .auth(email, pwd)\n                        .send({ name: \"MyCustomNamespace\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) }\n                            if (log) { console.log(\"create new namespace res.body: \", res.body) }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            should.not.exist(res.body._id)\n                            should.exist(res.body.id)\n                            expect(res.body.name).to.equal('MyCustomNamespace');\n                            should.exist(res.body.engine)\n                            expect(res.body.engine.name).to.equal('pinecone');\n                            expect(res.body.engine.type).to.equal('serverless');\n                            expect(res.body.engine.vector_size).to.equal(1536);\n                            expect(res.body.engine.index_name).to.equal('test-index');\n                            should.exist(res.body.embedding);\n                            expect(res.body.embedding.provider).to.equal('openai')\n                            expect(res.body.embedding.name).to.equal('text-embedding-ada-002')\n                            expect(res.body.embedding.dimension).to.equal(1536)\n\n                            // Get again all namespace. A new default namespace should not be created.\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/kb/namespace/all')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('array');\n                                    expect(res.body.length).to.equal(1);\n                                    should.not.exist(res.body[0]._id);\n                                    should.exist(res.body[0].id);\n\n                                    done();\n                                })\n                        })\n                });\n            });\n        })\n\n        it('create-namespaces-with-engine-hybrid-rejected', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                    .post('/' + savedProject._id + '/kb/namespace')\n                    .auth(email, pwd)\n                    .send({ name: \"MyCustomNamespace\", hybrid: true })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err) }\n                        if (log) { console.log(\"create new namespace res.body: \", res.body) }\n\n                        res.should.have.status(403);\n                        res.body.should.be.a('object');\n                        expect(res.body.success).to.equal(false);\n                        expect(res.body.error).to.equal('Hybrid mode is not allowed for the current project');\n                        \n                        done();\n                    })\n                });\n            });\n        })\n        \n        it('create-namespaces-with-engine-hybrid-accepted', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/auth/signin')\n                        .send({ email: \"admin@tiledesk.com\", password: \"adminadmin\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) }\n                            if (log) { console.log(\"login with superadmin res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(true);\n                            expect(res.body.token).not.equal(null);\n\n                            let superadmin_token = res.body.token;\n\n\n                            chai.request(server)\n                                .put('/projects/' + savedProject._id)\n                                .set('Authorization', superadmin_token)\n                                .send({ profile: custom_profile_sample })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) }\n                                    if (log) { console.log(\"update project res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.profile.customization.hybrid).to.equal(true);\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + '/kb/namespace')\n                                        .auth(email, pwd)\n                                        .send({ name: \"MyCustomNamespace\", hybrid: true })\n                                        .end((err, res) => {\n                \n                                            if (err) { console.error(\"err: \", err) }\n                                            if (log) { console.log(\"create new namespace res.body: \", res.body) }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            should.not.exist(res.body._id)\n                                            should.exist(res.body.id)\n                                            expect(res.body.name).to.equal('MyCustomNamespace');\n\n                                            should.exist(res.body.engine)\n                                            expect(res.body.engine.name).to.equal('pinecone');\n                                            expect(res.body.engine.type).to.equal('serverless');\n                                            expect(res.body.engine.vector_size).to.equal(1536);\n                                            expect(res.body.engine.index_name).to.equal('test-index-hybrid');\n                                            should.exist(res.body.embedding);\n                                            expect(res.body.embedding.provider).to.equal('openai')\n                                            expect(res.body.embedding.name).to.equal('text-embedding-ada-002')\n                                            expect(res.body.embedding.dimension).to.equal(1536)\n                \n                                            done();\n                                        })\n                                })\n                            \n                        })\n                    \n                })\n            });\n        })\n\n        it('import-namespace', (done) => {\n            \n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-namespace-import\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n                            expect(res.body[0].name).to.equal(\"Default\");\n\n                            let namespace_id = res.body[0].id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/namespace/import/' + namespace_id)\n                                .auth(email, pwd)\n                                //.set('Content-Type', 'text/plain')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/exported_namespace.json')), 'exported_namespace.json')\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"import contents res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.message).to.equal(\"Contents imported successfully\");\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/kb/?namespace=' + namespace_id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"get namespace res.body: \", res.body); }\n                                            \n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.count).to.equal(3);\n                                            expect(res.body.kbs.length).to.equal(3);\n\n                                            let content_with_tags = res.body.kbs.find(kb => kb.source === 'Example content');\n                                            expect(content_with_tags.tags.length).to.equal(2);\n                                            expect(content_with_tags.tags[0]).to.equal('tag1');\n                                            expect(content_with_tags.tags[1]).to.equal('tag2');\n                                            \n                                            let content_without_tags = res.body.kbs.find(kb => kb.source !== 'Example content');\n                                            expect(content_without_tags.tags).to.equal(undefined);\n                                            \n                                            done();\n                                        })\n\n                                })\n\n                        })\n                })\n            })\n        })\n\n        /**\n         * Update namespaces\n         */\n        it('update-namespace', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    // Get all namespaces. Create default namespace and return.\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n                            expect(res.body.length).to.equal(1);\n                            expect(res.body[0].id).to.equal(savedProject._id.toString());\n                            expect(res.body[0].name).to.equal(\"Default\");\n\n                            let namespace_id = res.body[0].id;\n\n                            let new_settings = {\n                                model: 'gpt-4o',\n                                max_tokens: 256,\n                                temperature: 0.3,\n                                top_k: 6,\n                                context: \"You are an awesome AI Assistant.\"\n                            }\n\n                            // Update namespace\n                            chai.request(server)\n                                .put('/' + savedProject._id + '/kb/namespace/' + namespace_id)\n                                .auth(email, pwd)\n                                .send({ name: \"New Name\", preview_settings: new_settings })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) }\n                                    if (log) { console.log(\"update namespace res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    should.not.exist(res.body._id);\n                                    should.exist(res.body.id);\n                                    expect(res.body.name).to.equal('New Name');\n                                    expect(res.body.preview_settings.model).to.equal('gpt-4o')\n                                    expect(res.body.preview_settings.max_tokens).to.equal(256)\n                                    expect(res.body.preview_settings.temperature).to.equal(0.3)\n                                    expect(res.body.preview_settings.top_k).to.equal(6)\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n        })\n\n        /**\n         * Delete default namespace - Forbidden\n         */\n        it('fail-to-delete-default-namespace', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    // Get all namespaces. Create default namespace and return.\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n                            expect(res.body.length).to.equal(1);\n                            expect(res.body[0].id).to.equal(savedProject._id.toString());\n                            expect(res.body[0].name).to.equal(\"Default\");\n\n                            let namespace_id = res.body[0].id;\n\n                            // Update namespace\n                            chai.request(server)\n                                .delete('/' + savedProject._id + '/kb/namespace/' + namespace_id)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) }\n                                    if (log) { console.log(\"delete namespace res.body: \", res.body) }\n\n                                    res.should.have.status(403);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(false);\n                                    expect(res.body.error).to.equal('Default namespace cannot be deleted');\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n        })\n\n        it('get-chatbots-from-namespace', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create('test-faqkb-create', savedUser._id).then((savedProject) => {\n                    faqService.create(savedProject._id, savedUser._id, { name: \"testbot1\" }).then((savedBot1) => {\n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot2\" }).then((savedBot2) => {\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/kb/namespace/all')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    let namespace_id = res.body[0].id;\n                                    if (log) { console.log(\"namespace_id: \", namespace_id) }\n\n                                    let newFaq1 = new faq({\n                                        id_faq_kb: savedBot1._id,\n                                        id_project: savedProject._id,\n                                        intent_id: \"new-faq-1\",\n                                        createdBy: savedUser._id,\n                                        updatedBy: savedUser._id,\n                                        actions: [{ \"_tdActionType\": \"askgptv2\", \"_tdActionId\": \"f58212f9-1a8c-4623-b6fa-0f34e57d9999\", \"namespace\": namespace_id }]\n                                    })\n\n                                    newFaq1.save((err, saved1) => {\n                                        if (err) { console.error(\"err1: \", err) };\n                                        if (log) { console.log(\"faq1 saved: \", saved1) };\n\n                                        let newFaq2 = new faq({\n                                            id_faq_kb: savedBot2._id,\n                                            id_project: savedProject._id,\n                                            intent_id: \"new-faq-2\",\n                                            createdBy: savedUser._id,\n                                            updatedBy: savedUser._id,\n                                            actions: [{ \"_tdActionType\": \"reply\", \"_tdActionId\": \"f58212f9-1a8c-4623-b6fa-0f34e57d9998\" }]\n                                        })\n\n                                        newFaq2.save((err, saved2) => {\n                                            if (err) { console.error(\"err2: \", err) };\n                                            if (log) { console.log(\"faq2 saved: \", saved2) };\n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/kb/namespace/' + namespace_id + '/chatbots')\n                                                .auth(email, pwd)\n                                                .end((err, res) => {\n\n                                                    if (err) { console.error(\"err: \", err) };\n                                                    if (log) { console.log(\"get chatbots from namespace res.body: \", res.body) };\n                                                    \n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('array');\n                                                    expect(res.body.length).to.equal(1);\n                                                    expect(res.body[0]._id).to.equal((savedBot1._id).toString());\n                                                    expect(res.body[0].name).to.equal('testbot1');\n\n                                                    done();\n                                                })\n                                        })\n                                    })\n                                })\n\n                        })\n                    })\n                })\n            })\n        }).timeout(10000)\n\n    })\n\n    describe('Contents', () => {\n\n        it('add-new-content', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n                            expect(res.body[0].engine.index_name).to.equal('test-index');\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb = {\n                                name: \"example_name5\",\n                                type: \"url\",\n                                source: \"https://www.exampleurl5.com\",\n                                content: \"\",\n                                namespace: namespace_id,\n                                tags: [\"test\", \"example\"]\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb')\n                                .auth(email, pwd)\n                                .send(kb)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.message).to.equal(\"Schedule scrape skipped in test environment\");\n\n                                    let realResponse = res.body.data;\n                                    expect(realResponse.lastErrorObject.updatedExisting).to.equal(false);\n                                    expect(realResponse.value.id_project).to.equal(namespace_id)\n                                    expect(realResponse.value.namespace).to.equal(namespace_id)\n                                    expect(realResponse.value.name).to.equal(\"example_name5\")\n                                    expect(realResponse.value.type).to.equal(\"url\")\n                                    expect(realResponse.value.source).to.equal(\"https://www.exampleurl5.com\")\n                                    expect(realResponse.value.status).to.equal(-1)\n                                    expect(realResponse.value.tags.length).to.equal(2);\n                                    expect(realResponse.value.tags[0]).to.equal(\"test\");\n                                    expect(realResponse.value.tags[1]).to.equal(\"example\");\n                                    should.not.exist(realResponse.engine)\n                                    should.not.exist(realResponse.value.engine)\n                                    should.not.exist(realResponse.embedding)\n                                    should.not.exist(realResponse.value.embedding)\n\n                                    let scheduleJson = res.body.schedule_json;\n                                    expect(scheduleJson.namespace).to.equal(namespace_id)\n                                    expect(scheduleJson.type).to.equal(\"url\")\n                                    expect(scheduleJson.source).to.equal(\"https://www.exampleurl5.com\")\n                                    expect(scheduleJson.hybrid).to.equal(false);\n                                    expect(scheduleJson.tags.length).to.equal(2);\n                                    expect(scheduleJson.tags[0]).to.equal(\"test\");\n                                    expect(scheduleJson.tags[1]).to.equal(\"example\");\n                                    should.exist(scheduleJson.engine)\n                                    should.exist(scheduleJson.embedding)\n\n                                    done();\n                                })\n                        })\n                });\n            });\n\n        })\n\n        it('add-new-text-content', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n                            if (log) { console.log(\"namespace_id: \", namespace_id); }\n\n                            let kb = {\n                                name: \"example_text1\",\n                                type: \"text\",\n                                source: \"example_text1\",\n                                content: \"Example text\",\n                                namespace: namespace_id,\n                                tags: [\"test\", \"example\"]\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb')\n                                .auth(email, pwd)\n                                .send(kb) // can be empty\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    let realResponse = res.body.data;\n                                    console.log(\"realResponse: \", realResponse);\n                                    expect(realResponse.value.id_project).to.equal(namespace_id)\n                                    expect(realResponse.value.name).to.equal(\"example_text1\")\n                                    expect(realResponse.value.type).to.equal(\"text\")\n                                    expect(realResponse.value.source).to.equal(\"example_text1\")\n                                    expect(realResponse.value.status).to.equal(-1)\n                                    expect(typeof realResponse.value.scrape_type === \"undefined\").to.be.true;\n                                    expect(typeof realResponse.value.scrape_options === \"undefined\").to.be.true;\n                                    expect(realResponse.value.tags.length).to.equal(2);\n                                    expect(realResponse.value.tags[0]).to.equal(\"test\");\n                                    expect(realResponse.value.tags[1]).to.equal(\"example\");\n                                    \n                                    done();\n                                })\n                        })\n                });\n            });\n\n        })\n\n        it('get-content-chunks', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb = {\n                                name: \"example_text1\",\n                                type: \"text\",\n                                source: \"example_text1\",\n                                content: \"Example text\",\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb')\n                                .auth(email, pwd)\n                                .send(kb) // can be empty\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    let realResponse = res.body.data;\n                                    let content_id = realResponse.value._id;\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/kb/namespace/' + namespace_id + '/chunks/' + content_id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err )};\n                                            if (log) { console.log(\"res.body: \", res.body )};\n\n                                            res.should.have.status(200);\n                                             /**\n                                             * Unable to verify the response due to an external request\n                                             */\n                                            expect(res.body.success).to.equal(true);\n                                            expect(res.body.message).to.equal(\"Get chunks skipped in test environment\");\n\n                                            done();\n                                        })\n                                })\n                        })\n                });\n            });\n        })\n\n        it('get-contents-with-queries', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    /**\n                     * Get all namespace. If no namespace exists, a default namespace is created and returned\n                     */\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body[0].name === 'Default');\n                            expect(res.body[0].id === savedProject._id);\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb1 = {\n                                name: \"example_name1\",\n                                type: \"url\",\n                                namespace: namespace_id,\n                                source: \"https://www.exampleurl1.com\",\n                                content: \"\"\n                            }\n\n                            let kb2 = {\n                                name: \"example_name2\",\n                                type: \"text\",\n                                namespace: namespace_id,\n                                source: \"example_name2\",\n                                content: \"example content\"\n                            }\n\n                            let kb3 = {\n                                name: \"example_name3\",\n                                type: \"url\",\n                                namespace: namespace_id,\n                                source: \"https://www.exampleurl3.com\",\n                                content: \"\"\n                            }\n\n\n                            /**\n                             * Add contents to default namespace\n                             */\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/kb\")\n                                .auth(email, pwd)\n                                .send(kb1)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb1 res.body: \", res.body); }\n                                    res.should.have.status(200);\n\n                                    setTimeout(() => {\n                                        chai.request(server)\n                                            .post('/' + savedProject._id + \"/kb\")\n                                            .auth(email, pwd)\n                                            .send(kb2)\n                                            .end((err, res) => {\n\n                                                if (err) { console.error(\"err: \", err); }\n                                                if (log) { console.log(\"create kb2 res.body: \", res.body); }\n\n                                                res.should.have.status(200);\n\n                                                setTimeout(() => {\n                                                    chai.request(server)\n                                                        .post('/' + savedProject._id + \"/kb\")\n                                                        .auth(email, pwd)\n                                                        .send(kb3)\n                                                        .end((err, res) => {\n\n                                                            if (err) { console.error(\"err: \", err); }\n                                                            if (log) { console.log(\"create kb3 res.body: \", res.body); }\n\n                                                            res.should.have.status(200);\n\n                                                            let query = \"?status=-1&type=url&limit=5&page=0&direction=-1&sortField=updatedAt&search=example&namespace=\" + namespace_id;\n                                                            //let query = \"?namespace=\" + namespace_id;\n\n                                                            chai.request(server)\n                                                                .get('/' + savedProject._id + \"/kb\" + query)\n                                                                .auth(email, pwd)\n                                                                .end((err, res) => {\n\n                                                                    if (err) { console.error(\"err: \", err)}\n                                                                    if (log) { console.log(\"getall res.body: \", res.body); }\n\n                                                                    res.should.have.status(200);\n                                                                    res.body.should.be.a('object');\n                                                                    res.body.kbs.should.be.a('array');\n                                                                    expect(res.body.kbs.length).to.equal(2);\n                                                                    expect(res.body.count).to.equal(2);\n                                                                    res.body.query.should.be.a('object');\n                                                                    expect(res.body.query.status).to.equal(-1);\n                                                                    expect(res.body.query.limit).to.equal(5);\n                                                                    expect(res.body.query.page).to.equal(0);\n                                                                    expect(res.body.query.direction).to.equal(-1);\n                                                                    expect(res.body.query.sortField).to.equal(\"updatedAt\");\n                                                                    expect(res.body.query.search).to.equal(\"example\");\n\n                                                                    done();\n\n                                                                })\n\n                                                        })\n                                                }, 1000)\n                                            })\n                                    }, 1000)\n                                })\n                        })\n                })\n            })\n        }).timeout(20000)\n\n        it('get-contents-with-queries-namespace-not-belong-project', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    /**\n                     * Get all namespace. If no namespace exists, a default namespace is created and returned\n                     */\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body[0].name === 'Default');\n                            expect(res.body[0].id === savedProject._id);\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb1 = {\n                                name: \"example_name1\",\n                                type: \"url\",\n                                namespace: namespace_id,\n                                source: \"https://www.exampleurl1.com\",\n                                content: \"\"\n                            }\n\n                            /**\n                             * Add contents to default namespace\n                             */\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/kb\")\n                                .auth(email, pwd)\n                                .send(kb1)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb1 res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    let namespace_id = \"fakenamespaceid\";\n\n                                    let query = \"?status=100&type=url&limit=5&page=0&direction=-1&sortField=updatedAt&search=example&namespace=\" + namespace_id;\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + \"/kb\" + query)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"getall res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            res.body.kbs.should.be.a('array');\n                                            expect(res.body.kbs.length).to.equal(0);\n                                            expect(res.body.count).to.equal(0);\n\n                                            done();\n\n                                        })\n                                })\n                        })\n                })\n            })\n        }).timeout(20000)\n\n        it('add-single-faq-success', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            let content = {\n                                name: \"Sample question\",\n                                source: \"Sample question\",\n                                content: \"Sample question\\nSample answer\",\n                                type: \"faq\",\n                                tags: [\"tag1\", \"tag2\"],\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb')\n                                .auth(email, pwd)\n                                .send(content)\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    let realResponse = res.body.data;\n                                    expect(realResponse.value.namespace).to.equal(namespace_id);\n                                    expect(realResponse.value.type).to.equal(\"faq\");\n                                    expect(realResponse.value.source).to.equal(\"Sample question\");\n                                    expect(realResponse.value.content).to.equal(\"Sample question\\nSample answer\");\n                                    expect(realResponse.value.tags.length).to.equal(2);\n                                    expect(realResponse.value.tags[0]).to.equal(\"tag1\");\n                                    expect(realResponse.value.tags[1]).to.equal(\"tag2\");\n\n                                    done();\n\n\n                                });\n\n                        });\n\n\n                    })\n                });\n        })\n\n        it('add-multiple-faqs-with-csv', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n                            let namespace = res.body[0];\n                            let namespace_id = namespace.id;\n\n                            expect(namespace.engine.name).to.equal(\"pinecone\");\n                            expect(namespace.engine.type).to.equal(\"serverless\");\n                            expect(namespace.engine.index_name).to.equal(\"test-index\");\n                            expect(namespace.embedding.provider).to.equal(\"openai\");\n                            expect(namespace.embedding.name).to.equal(\"text-embedding-ada-002\");\n                            expect(namespace.embedding.dimension).to.equal(1536);\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/csv?namespace=' + namespace_id)\n                                .auth(email, pwd)\n                                //.set('Content-Type', 'text/csv')\n                                .field('delimiter', ';')\n                                .field('tags', JSON.stringify(['tag1', 'tag2']))\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/example-kb-faqs.csv')), 'example-kb-faqs.csv')\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n                                    console.log(\"res.body: \", res.body)\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.message).to.equal(\"Schedule scrape skipped in test environment\");\n\n                                    let realResponse = res.body.data;\n                                    realResponse.should.be.a('array');\n                                    console.log(\"realResponse: \", realResponse[0]);\n                                    expect(realResponse.length).to.equal(2);\n                                    expect(realResponse[0].namespace).to.equal(namespace_id);\n                                    expect(realResponse[0].type).to.equal('faq');\n                                    expect(realResponse[0].source).to.equal('Question 1');\n                                    should.not.exist(realResponse[0].engine)\n                                    should.not.exist(realResponse[0].embedding)\n                                    expect(realResponse[1].namespace).to.equal(namespace_id);\n                                    expect(realResponse[1].type).to.equal('faq');\n                                    expect(realResponse[1].source).to.equal('Question 2');\n                                    should.not.exist(realResponse[1].engine)\n                                    should.not.exist(realResponse[1].embedding)\n\n                                    let scheduleJson = res.body.schedule_json;\n                                    scheduleJson.should.be.a('array');\n                                    expect(scheduleJson.length).to.equal(2);\n                                    expect(scheduleJson[0].namespace).to.equal(namespace_id);\n                                    expect(scheduleJson[0].type).to.equal('faq');\n                                    expect(scheduleJson[0].source).to.equal('Question 1');\n                                    should.exist(scheduleJson[0].engine)\n                                    should.exist(scheduleJson[0].embedding)\n                                    expect(scheduleJson[0].engine.index_name).to.equal(namespace.engine.index_name);\n                                    expect(scheduleJson[0].embedding.provider).to.equal(namespace.embedding.provider);\n                                    expect(scheduleJson[0].embedding.name).to.equal(namespace.embedding.name);\n\n                                    expect(scheduleJson[1].namespace).to.equal(namespace_id);\n                                    expect(scheduleJson[1].type).to.equal('faq');\n                                    expect(scheduleJson[1].source).to.equal('Question 2');\n                                    should.exist(scheduleJson[0].engine)\n                                    should.exist(scheduleJson[0].embedding)\n\n                                    done();\n                                })\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        /**\n         * If you try to add content to a project that has no namespace, it returns 403 forbidden.\n         */\n        it('add-multiple-urls-no-namespaces', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kb/multi?namespace=123456')\n                        .auth(email, pwd)\n                        //.set('Content-Type', 'text/plain')\n                        .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')), 'kbUrlsList.txt')\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(404);\n                            res.should.be.a('object')\n                            expect(res.body.success).to.equal(false);\n                            let error_response = \"Namespace not found with id 123456\"\n                            expect(res.body.error).to.equal(error_response);\n\n                            done();\n\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        /**\n         * If you try to add content to a namespace that does not belong to the selected project and \n         * the project has at least one namesapce, it returns 403 forbidden.\n         */\n        it('add-multiple-urls-namespace-not-belong-project', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/multi?namespace=fakenamespaceid')\n                                .auth(email, pwd)\n                                //.set('Content-Type', 'text/plain')\n                                .attach('uploadFile', fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')), 'kbUrlsList.txt')\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    res.should.have.status(404);\n                                    res.should.be.a('object');\n                                    expect(res.body.success).to.equal(false);\n                                    let error_response = \"Namespace not found with id fakenamespaceid\";\n                                    expect(res.body.error).to.equal(error_response);\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        it('add-multiple-urls-success', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace = res.body[0];\n                            let namespace_id = namespace.id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/multi?namespace=' + namespace_id)\n                                .auth(email, pwd)\n                                .field('refresh_rate', 'never')\n                                .field('scrape_type', '2')\n                                .field('tags', JSON.stringify(['tag1', 'tag2']))\n                                .attach(\n                                    'uploadFile',\n                                    fs.readFileSync(path.resolve(__dirname, './fixtures/kbUrlsList.txt')),\n                                    'kbUrlsList.txt'\n                                )\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n\n                                    let realResponse = res.body.result;\n                                    expect(realResponse.length).to.equal(4);\n                                    expect(realResponse[0].namespace).to.equal(namespace_id);\n                                    expect(realResponse[0].source).to.equal('https://gethelp.tiledesk.com/articles/article1');\n                                    expect(realResponse[0].tags.length).to.equal(2);\n                                    expect(realResponse[0].tags[0]).to.equal('tag1');\n                                    expect(realResponse[0].tags[1]).to.equal('tag2');\n                                    should.not.exist(realResponse[0].engine);\n                                    should.not.exist(realResponse[0].embedding);\n                                    expect(realResponse[1].namespace).to.equal(namespace_id);\n                                    expect(realResponse[1].source).to.equal('https://gethelp.tiledesk.com/articles/article2');\n                                    expect(realResponse[1].tags.length).to.equal(2);\n                                    expect(realResponse[1].tags[0]).to.equal('tag1');\n                                    expect(realResponse[1].tags[1]).to.equal('tag2');\n\n                                    let scheduleJson = res.body.schedule_json;\n                                    expect(scheduleJson.length).to.equal(4);\n                                    expect(scheduleJson[0].namespace).to.equal(namespace_id);\n                                    expect(scheduleJson[0].source).to.equal('https://gethelp.tiledesk.com/articles/article1');\n                                    expect(scheduleJson[0].tags.length).to.equal(2);\n                                    expect(scheduleJson[0].tags[0]).to.equal('tag1');\n                                    expect(scheduleJson[0].tags[1]).to.equal('tag2');\n                                    should.exist(scheduleJson[0].engine);\n                                    should.exist(scheduleJson[0].embedding);\n                                    expect(scheduleJson[0].engine.index_name).to.equal(namespace.engine.index_name);\n                                    expect(scheduleJson[0].embedding.provider).to.equal(namespace.embedding.provider);\n                                    expect(scheduleJson[0].embedding.name).to.equal(namespace.embedding.name);\n\n                                    expect(scheduleJson[1].namespace).to.equal(namespace_id);\n                                    expect(scheduleJson[1].source).to.equal('https://gethelp.tiledesk.com/articles/article2');\n                                    expect(scheduleJson[1].tags.length).to.equal(2);\n                                    expect(scheduleJson[1].tags[0]).to.equal('tag1');\n                                    expect(scheduleJson[1].tags[1]).to.equal('tag2');\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        it('add-multiple-urls-with-scrape-option-success-type-4', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/multi?namespace=' + namespace_id)\n                                .auth(email, pwd)\n                                .send({ list:[\"https://gethelp.tiledesk.com/article\"], scrape_type: 4,  scrape_options: { tags_to_extract: [\"article\",\"p\"], unwanted_tags:[\"script\",\"style\"], unwanted_classnames:[\"header\",\"related-articles\"]}})\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n\n                                    let realResponse = res.body.result;\n                                    expect(realResponse.length).to.equal(1)\n                                    expect(realResponse[0].scrape_type).to.equal(4)\n                                    expect(typeof realResponse[0].scrape_options === \"undefined\").to.be.false;\n                                    expect(realResponse[0].scrape_options.tags_to_extract.length).to.equal(2);\n                                    expect(realResponse[0].scrape_options.unwanted_tags.length).to.equal(2);\n                                    expect(realResponse[0].scrape_options.unwanted_classnames.length).to.equal(2);\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        it('add-multiple-urls-with-scrape-option-success-type-3', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/multi?namespace=' + namespace_id)\n                                .auth(email, pwd)\n                                .send({ list:[\"https://gethelp.tiledesk.com/article\"], refresh_rate: 'daily', scrape_type: 3 })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    res.should.have.status(200);\n\n                                    let realResponse = res.body.result;\n                                    expect(realResponse.length).to.equal(1)\n                                    expect(realResponse[0].scrape_type).to.equal(3)\n                                    expect(typeof realResponse[0].scrape_options === null);\n\n                                    done();\n\n                                })\n                        })\n                });\n            });\n\n        }).timeout(10000)\n\n        it('expand-sitemap', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kb/sitemap')\n                        .auth(email, pwd)\n                        // .send({ sitemap: \"https://www.wired.it/sitemap.xml\" })\n                        .send({ sitemap: \"https://gethelp.tiledesk.com/sitemap.xml\" })\n                        .end((err, res) => {\n\n                            if (err) { console.log(\"error: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            res.body.sites.should.be.a('array');\n\n                            done();\n\n                        })\n\n                });\n            });\n\n        }).timeout(10000)\n\n        it('import-sitemap', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n          \n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-kb-import-sitemap\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            let content = {\n                                name: \"https://www.sitemaps.org/sitemap.xml\",\n                                type: \"sitemap\",\n                                source: \"https://www.sitemaps.org/sitemap.xml\",\n                                content: \"\",\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/sitemap/import?namespace=' + namespace_id)\n                                .auth(email, pwd)\n                                .send(content)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body) }\n\n                                    let realResponse = res.body.result;\n                                    realResponse.should.be.a('array');\n                                    should.exist(realResponse[0]._id);\n                                    should.exist(realResponse[0].sitemap_origin_id);\n                                    let sitemap_content = realResponse.find(e => e.type === 'sitemap');\n                                    expect(sitemap_content).not.equal(null);\n                                    expect(realResponse[0].sitemap_origin).to.equal(\"https://www.sitemaps.org/sitemap.xml\");\n\n                                    let scheduleJson = res.body.schedule_json;\n                                    scheduleJson.should.be.a('array');\n                                    should.exist(scheduleJson[0].engine)\n                                    should.exist(scheduleJson[0].embedding)\n\n                                    done();\n                                })\n\n                        })\n                })\n            })\n        }).timeout(3000)\n\n        it('scrape-single', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body) }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n                            expect(res.body[0].id_project).to.equal(savedProject._id.toString())\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb = {\n                                name: \"https://www.exampleurl6.com\",\n                                type: \"url\",\n                                source: \"https://www.exampleurl6.com\",\n                                content: \"\",\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb')\n                                .auth(email, pwd)\n                                .send(kb)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create kb res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    \n                                    let realResponse = res.body.data;\n                                    let savedKb = realResponse.value;\n                                    expect(savedKb.id_project).to.equal(savedProject._id.toString())\n\n                                    kb.id = realResponse.value._id;\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + \"/kb/scrape/single\")\n                                        .auth(email, pwd)\n                                        .send(kb)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"single scrape res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(true);\n                                            expect(res.body.message).to.equal(\"Skip indexing in test environment\");\n                                            expect(res.body.data.type).to.equal('url');\n                                            expect(res.body.data.namespace).to.equal(namespace_id);\n                                            should.exist(res.body.data.engine);\n                                            expect(res.body.data.engine.index_name).to.equal(\"test-index\");\n                                            expect(res.body.data.engine.vector_size).to.equal(1536);\n                                            should.exist(res.body.data.embedding);\n                                            expect(res.body.data.embedding.provider).to.equal(\"openai\");\n                                            expect(res.body.data.embedding.name).to.equal(\"text-embedding-ada-002\");\n                                            expect(res.body.data.embedding.dimension).to.equal(1536);\n\n                                            done();\n\n                                        })\n                                })\n                        })\n                });\n            });\n        }).timeout(5000);\n\n        it('askkb-key-from-env', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-kb-qa\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200)\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/kb/qa\")\n                                .auth(email, pwd)\n                                .send({ model: \"gpt-4o\", namespace: savedProject._id, question: \"sample question\", advancedPrompt: true, system_context: \"You are a robot coming from future\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200)\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.message).to.equal(\"Question skipped in test environment\");\n                                    res.body.data.model.should.be.a('object');\n                                    expect(res.body.data.model.provider).to.equal(\"openai\")\n                                    expect(res.body.data.model.name).to.equal(\"gpt-4o\")\n                                    expect(res.body.data.model.api_key).to.equal(\"fakegptkey\");\n                                    expect(res.body.data.question).to.equal(\"sample question\");\n                                    should.exist(res.body.data.engine);\n                                    should.exist(res.body.data.embedding);\n                                    expect(res.body.data.embedding.api_key).to.equal(\"fakegptkey\");\n\n                                    done();\n                                })\n\n\n                        })\n                })\n            })\n        }).timeout(10000)\n\n        it('askkb-with-hybrid-search', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-kb-qa\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get all namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200)\n                            expect(res.body.length).to.equal(1);\n                            expect(res.body[0].type === \"serverless\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/kb/qa\")\n                                .auth(email, pwd)\n                                .send({ model: \"gpt-4o\", namespace: savedProject._id, question: \"sample question\", advancedPrompt: true, system_context: \"You are a robot coming from future\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    expect(res.body.data);\n                                    expect(res.body.success).to.equal(true);\n                                    expect(res.body.message).to.equal(\"Question skipped in test environment\");\n                                    res.body.data.model.should.be.a('object');\n                                    expect(res.body.data.model.provider).to.equal(\"openai\")\n                                    expect(res.body.data.model.name).to.equal(\"gpt-4o\")\n                                    expect(res.body.data.model.api_key).to.equal(\"fakegptkey\");\n                                    expect(res.body.data.search_type === \"hybrid\");\n                                    expect(res.body.data.question).to.equal(\"sample question\");\n                                    should.exist(res.body.data.engine);\n                                    should.exist(res.body.data.embedding);\n                                    expect(res.body.data.embedding.api_key).to.equal(\"fakegptkey\");\n\n                                    done();\n                                })\n\n\n                        })\n                })\n            })\n        }).timeout(10000)\n\n        it('webhook', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-kb-webhook\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.log(\"error: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb = {\n                                name: \"example_name6\",\n                                type: \"url\",\n                                source: \"https://www.exampleurl6.com\",\n                                content: \"\",\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/')\n                                .auth(email, pwd)\n                                .send(kb)\n                                .end((err, res) => {\n\n                                    if (err) { console.log(\"error: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    let realResponse = res.body.data;\n                                    let kb_id = realResponse.value._id;\n\n                                    chai.request(server)\n                                        .post('/webhook/kb/status')\n                                        .set(\"x-auth-token\", \"testtoken\")\n                                        .send({ id: kb_id, status: 300 })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err) };\n                                            if (log) { console.log(\"res.body: \", res.body) };\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.status).to.equal(300);\n\n                                            done();\n\n                                        })\n\n\n                                })\n                        })\n\n\n\n\n                });\n            });\n        }).timeout(10000)\n\n        it('webhook-reindex', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-kb-webhook\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.log(\"error: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('array');\n\n                            let namespace_id = res.body[0].id;\n\n                            let kb = {\n                                name: \"example_name6\",\n                                type: \"url\",\n                                source: \"https://www.exampleurl6.com\",\n                                content: \"\",\n                                namespace: namespace_id\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/')\n                                .auth(email, pwd)\n                                .send(kb)\n                                .end((err, res) => {\n\n                                    if (err) { console.log(\"error: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    let realResponse = res.body.data;\n                                    let kb_id = realResponse.value._id;\n\n                                    chai.request(server)\n                                        .post('/webhook/kb/reindex')\n                                        .set(\"x-auth-token\", \"testtoken\")\n                                        .send({ content_id: kb_id })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err) };\n                                            if (log) { console.log(\"res.body: \", res.body) };\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.equal(true);\n                                            expect(res.body.message).to.equal(\"Content queued for reindexing\");\n\n                                            done();\n\n                                        })\n\n\n                                })\n                        })\n\n\n\n\n                });\n            });\n        }).timeout(10000)\n\n    })\n\n    \n    \n\n    describe('Unanswered Questions', () => {\n        \n        it('add-unanswered-question', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            \n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            let data = {\n                                namespace: namespace_id,\n                                question: \"Come funziona il prodotto?\"\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/unanswered')\n                                .auth(email, pwd)\n                                .send(data)\n                                .end((err, res) => {\n                                    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"create unanswered question res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.namespace).to.equal(namespace_id);\n                                    expect(res.body.question).to.equal(\"Come funziona il prodotto?\");\n                                    expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n        it('get-unanswered-questions', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-get\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get namespaces res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            // First add a question\n                            let data = {\n                                namespace: namespace_id,\n                                question: \"Come funziona il prodotto?\"\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/unanswered')\n                                .auth(email, pwd)\n                                .send(data)\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"add unanswered question res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n\n                                    // Then get all questions\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"get unanswered questions res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.count).to.equal(1);\n                                            expect(res.body.questions).to.be.an('array');\n                                            expect(res.body.questions[0].question).to.equal(\"Come funziona il prodotto?\");\n                                            expect(res.body.questions[0].namespace).to.equal(namespace_id);\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n        it('delete-unanswered-question', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-delete\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            // First add a question\n                            let question = {\n                                namespace: namespace_id,\n                                question: \"Come funziona il prodotto?\"\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/unanswered')\n                                .auth(email, pwd)\n                                .send(question)\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    res.should.have.status(200);\n                                    let questionId = res.body._id;\n\n                                    // Then delete it\n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/kb/unanswered/' + questionId)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.success).to.be.true;\n                                            expect(res.body.message).to.equal(\"Question deleted successfully\");\n\n                                            // Verify it's deleted\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)\n                                                .auth(email, pwd)\n                                                .end((err, res) => {\n                                                    if (err) { console.error(\"err: \", err); }\n                                                    res.should.have.status(200);\n                                                    expect(res.body.count).to.equal(0);\n                                                    expect(res.body.questions).to.be.an('array').that.is.empty;\n                                                    done();\n                                                });\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n        it('delete-all-unanswered-questions', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-delete-all\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            // First add two questions\n                            let questions = [\n                                {\n                                    namespace: namespace_id,\n                                    question: \"Come funziona il prodotto?\"\n                                },\n                                {\n                                    namespace: namespace_id,\n                                    question: \"Quali sono i prezzi?\"\n                                }\n                            ];\n\n                            Promise.all(questions.map(q => \n                                chai.request(server)\n                                    .post('/' + savedProject._id + '/kb/unanswered')\n                                    .auth(email, pwd)\n                                    .send(q)\n                            )).then(() => {\n                                // Then delete all questions\n                                chai.request(server)\n                                    .delete('/' + savedProject._id + '/kb/unanswered/namespace/' + namespace_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n                                        if (err) { console.error(\"err: \", err); }\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.be.true;\n                                        expect(res.body.count).to.equal(2);\n                                        expect(res.body.message).to.equal(\"All questions deleted successfully\");\n\n                                        // Verify they're deleted\n                                        chai.request(server)\n                                            .get('/' + savedProject._id + '/kb/unanswered/' + namespace_id)\n                                            .auth(email, pwd)\n                                            .end((err, res) => {\n                                                if (err) { console.error(\"err: \", err); }\n                                                res.should.have.status(200);\n                                                expect(res.body.count).to.equal(0);\n                                                expect(res.body.questions).to.be.an('array').that.is.empty;\n                                                done();\n                                            });\n                                    });\n                            });\n                        });\n                });\n            });\n        });\n\n        it('update-unanswered-question', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-update\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            // First add a question\n                            let question = {\n                                namespace: namespace_id,\n                                question: \"Come funziona il prodotto?\"\n                            }\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/kb/unanswered')\n                                .auth(email, pwd)\n                                .send(question)\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err: \", err); }\n                                    res.should.have.status(200);\n                                    let questionId = res.body._id;\n\n                                    // Then update it\n                                    chai.request(server)\n                                        .put('/' + savedProject._id + '/kb/unanswered/' + questionId)\n                                        .auth(email, pwd)\n                                        .send({ question: \"Come funziona il prodotto aggiornato?\" })\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err: \", err); }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.question).to.equal(\"Come funziona il prodotto aggiornato?\");\n                                            expect(res.body.namespace).to.equal(namespace_id);\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n        it('count-unanswered-questions', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-unanswered-count\", savedUser._id).then(function (savedProject) {\n                    chai.request(server)\n                        .get('/' + savedProject._id + '/kb/namespace/all')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            res.should.have.status(200);\n                            expect(res.body.length).to.equal(1);\n\n                            let namespace_id = res.body[0].id;\n\n                            // First add two questions\n                            let questions = [\n                                {\n                                    namespace: namespace_id,\n                                    question: \"Come funziona il prodotto?\"\n                                },\n                                {\n                                    namespace: namespace_id,\n                                    question: \"Quali sono i prezzi?\"\n                                }\n                            ];\n\n                            Promise.all(questions.map(q => \n                                chai.request(server)\n                                    .post('/' + savedProject._id + '/kb/unanswered')\n                                    .auth(email, pwd)\n                                    .send(q)\n                            )).then(() => {\n                                // Then count them\n                                chai.request(server)\n                                    .get('/' + savedProject._id + '/kb/unanswered/count/' + namespace_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n                                        if (err) { console.error(\"err: \", err); }\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.count).to.equal(2);\n                                        done();\n                                    });\n                            });\n                        });\n                });\n            });\n        });\n    });\n\n});\n"
  },
  {
    "path": "test/kbsettingsRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar userService = require('../services/userService');\nvar projectService = require('../services/projectService');\n\nlet log = false;\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('KbSettingsRoute', () => {\n\n    describe('/create', () => {\n\n        it('createKbSettings', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kbsettings')\n                        .auth(email, pwd)\n                        .send({}) // can be empty\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + \"/kbsettings\")\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"get kbsettings res.body: \", res.body); }\n                                    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_project).to.equal(savedProject._id.toString())\n                                    expect(res.body.maxKbsNumber).to.equal(3);\n                                    expect(res.body.maxPagesNumber).to.equal(1000);\n                                    expect(res.body.kbs).is.an('array').that.is.empty;\n\n                                    done();\n                                })\n\n                        })\n\n                });\n            });\n\n        });\n\n        it('createKbSettingsIfNotExists', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .get('/' + savedProject._id + \"/kbsettings\")\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"get kbsettings res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject._id.toString())\n                            expect(res.body.maxKbsNumber).to.equal(3);\n                            expect(res.body.maxPagesNumber).to.equal(1000);\n                            expect(res.body.kbs).is.an('array').that.is.empty;\n\n                            done();\n                        })\n                });\n            });\n\n        });\n\n\n        it('addKbToKbSettings', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kbsettings')\n                        .auth(email, pwd)\n                        .send({}) // can be empty\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n                            \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n                            let settings_id = res.body._id;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + \"/kbsettings/\" + settings_id)\n                                .auth(email, pwd)\n                                .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n                                .end((err, res) => {\n                                    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n                                    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    //expect(res.body.kbs).to.have.length(1)\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + \"/kbsettings/\" + settings_id)\n                                        .auth(email, pwd)\n                                        .send({ name: \"secondurl.com/support/\", url: \"https://secondurl.com/support/\" })\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            //expect(res.body.kbs).to.have.length(2)\n\n                                            done();\n                                        })\n\n                                })\n                        })\n\n                });\n            });\n\n        });\n\n        it('updateKbSettings', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/kbsettings')\n                        .auth(email, pwd)\n                        .send({}) // can be empty\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            chai.request(server)\n                                .put('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n                                .auth(email, pwd)\n                                .send({ gptkey: \"sk-12345678\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n                                    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n\n                                    done();\n\n                                })\n                        })\n\n                });\n            });\n\n        });\n\n        // it('deleteKbFromList', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     let settings_id = res.body._id;\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + settings_id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             //expect(res.body.kbs).to.have.length(1)\n\n        //                             let kb_to_delete_id = res.body._id;\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/\" + settings_id)\n        //                                 .auth(email, pwd)\n        //                                 .send({ name: \"secondurl.com/support/\", url: \"https://secondurl.com/support/\" })\n        //                                 .end((err, res) => {\n        //                                     if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                                     res.should.have.status(200);\n        //                                     res.body.should.be.a('object');\n        //                                     //expect(res.body.kbs).to.have.length(2)\n\n        //                                     chai.request(server)\n        //                                         .delete('/' + savedProject._id + \"/kbsettings/\" + settings_id + \"/\" + kb_to_delete_id)\n        //                                         .auth(email, pwd)\n        //                                         .end((err, res) => {\n        //                                             if (log) { console.log(\"delete kb res.body: \", res.body); };\n        //                                             res.should.have.status(200);\n        //                                             res.body.should.be.a('object');\n        //                                             expect(res.body.kbs).to.have.length(1)\n        //                                             expect(res.body.kbs[0].name).to.equal(\"secondurl.com/support/\");\n        //                                             expect(res.body.kbs[0].url).to.equal(\"https://secondurl.com/support/\");\n\n        //                                             done();\n        //                                         })\n        //                                 })\n\n        //                         })\n        //                 })\n        //         });\n        //     });\n\n        // });\n\n\n\n        // THE FOLLOWING TEST REQUIRES REAL REQUESTS\n        // it('start scrape', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             expect(res.body.kbs).to.have.length(1)\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/startscrape\")\n        //                                 .auth(email, pwd)\n        //                                 .send({ full_url: \"https://developer.tiledesk.com/\", gptkey: \"valid-key\" })\n        //                                 .end( async (err, res) => {\n        //                                     if (err) {\n        //                                         console.log(\" test err: \", err);\n        //                                     }\n        //                                     if (log) { console.log(\"start scrape res.body: \", res.body); }\n\n        //                                     res.should.have.status(200);\n        //                                     res.body.should.be.a('object');\n        //                                     expect(res.body.message).to.not.equal(null);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n\n        // it('start scrape error - invalid key', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             expect(res.body.kbs).to.have.length(1)\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/startscrape\")\n        //                                 .auth(email, pwd)\n        //                                 .send({ full_url: \"https://developer.tiledesk.com/\", gptkey: \"invalid-gptkey\" })\n        //                                 .end( async (err, res) => {\n        //                                     if (err) {\n        //                                         console.log(\" test err: \", err);\n        //                                     }\n        //                                     if (log) { console.log(\"start scrape res.body: \", res.body); }\n\n        //                                     res.should.have.status(200);\n        //                                     res.body.should.be.a('object');\n        //                                     expect(res.body.message).to.not.equal(null);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n\n        // it('start scrape error - missing parameter', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             expect(res.body.kbs).to.have.length(1)\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/startscrape\")\n        //                                 .auth(email, pwd)\n        //                                 // .send({ full_url: \"https://fakeurl.com/support\", gptkey: null })\n        //                                 // OR\n        //                                 .send({ full_url: \"https://fakeurl.com/support\" })\n        //                                 .end((err, res) => {\n        //                                     res.should.have.status(422);\n        //                                     res.body.should.be.a('object');\n        //                                     expect(res.body.statusText).to.equal(\"Unprocessable Entity\");\n        //                                     expect(res.body.detail).to.not.equal(null);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n\n        // it('check status error - missing parameter', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"exampleurl.com/kb/\", url: \"https://exampleurl.com/kb/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             expect(res.body.kbs).to.have.length(1)\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/checkstatus\")\n        //                                 .auth(email, pwd)\n        //                                 // .send()\n        //                                 // OR\n        //                                 .send({ full_url: null })\n        //                                 .end((err, res) => {\n        //                                     res.should.have.status(422);\n        //                                     res.body.should.be.a('object');\n        //                                     expect(res.body.statusText).to.equal(\"Unprocessable Entity\");\n        //                                     expect(res.body.detail).to.not.equal(null);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n\n        // it('checkstatuserror - no db created for', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n        //                     expect(res.body.id_project).to.equal(savedProject._id.toString());\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ name: \"gethelp.tiledesk.com/\", url: \"https://gethelp.tiledesk.com/\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n        //                             expect(res.body.kbs).to.have.length(1)\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + \"/kbsettings/checkstatus\")\n        //                                 .auth(email, pwd)\n        //                                 .send({ full_url: \"https://gethelp.tiledesk.com/\" })\n        //                                 .end((err, res) => {\n        //                                     res.should.have.status(200);\n        //                                     res.body.should.be.a('object');\n        //                                     // expect(res.body.status_message).to.equal(\"Database is not created yet for dbnevercreated.com/kb/, please wait a few minutes and try again\");\n        //                                     // expect(res.body.status_code).to.equal(0);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n        \n        // it('qa error', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        //         projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + \"/kbsettings/qa\")\n        //                 .auth(email, pwd)\n        //                 .send({ question: \"How to connect Tiledesk with Telegram?\", kbid: \"https://gethelp.tiledesk.com/\", gptkey: \"valid-key\" })\n        //                 .end((err, res) => {\n        //                     if (log) {}\n        //                     console.log(\"qa res.body: \", res.body);\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n\n        //                     done();\n        //                 })\n\n        //         });\n        //     });\n\n        // }).timeout(20000)\n\n    });\n\n});\n\n\n"
  },
  {
    "path": "test/keysRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('KeysRoute', () => {\n\n  describe('/generate', () => {\n \n    it('generate', (done) => {\n\n       var email = \"test-signup-\" + Date.now() + \"@email.com\";\n       var pwd = \"pwd\";\n\n        userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n            projectService.create(\"test-join-member\", savedUser._id).then(function(savedProject) {                                              \n                    chai.request(server)\n                        .post('/'+ savedProject._id + '/keys/generate')\n                        .auth(email, pwd)\n                        .send()\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\",  res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                        \n                            done();\n                        });\n\n                        \n                });\n                });\n                \n    }).timeout(20000);\n\n\n\n\n\n\n\n\n});\n\n});\n\n\n"
  },
  {
    "path": "test/labelRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nlet log = false;\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('LabelRoute', () => {\n\n    describe('/clone', () => {\n\n        it('clone', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { if (err) { console.error(\"err\", err); } }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n                            done();\n                        });\n                });\n            });\n        });\n\n\n        it('cloneENAndGetByLanguageEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/labels/EN')\n                                .auth(email, pwd)\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n                                    expect(res.body.default).to.equal(true);\n                                    expect(res.body.lang).to.equal(\"EN\");\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        it('CloneENAndGetByLanguageIT', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/labels/IT')\n                                .auth(email, pwd)\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n                                    //expect(res.body.default).to.equal(false);\n                                    expect(res.body.lang).to.equal(\"EN\");\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        it('cloneENAndGetByLanguageAR', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n                            expect(res.body.data[0].default).to.equal(true);\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/labels/AR')\n                                .auth(email, pwd)\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n                                    //expect(res.body.default).to.equal(false);\n                                    expect(res.body.lang).to.equal(\"EN\");\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        it('cloneARAndGetByLanguageAR', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"ARR\" }) //not exists\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].lang).to.equal(\"ARR\");\n\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/labels/ARR')\n                                .auth(email, pwd)\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body ar\", res.body); }\n\n                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n                                    expect(res.body.lang).to.equal(\"ARR\");\n                                    expect(res.body.default).to.equal(true);\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'cloneITAndgetByLanguageEN'\n        it('cloneITAndgetByLanguageEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"IT\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"IT\");\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/labels/EN')\n                                .auth(email, pwd)\n                                .send()\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"Scrivi la tua domanda...\");\n                                    expect(res.body.lang).to.equal(\"IT\");\n                                    expect(res.body.default).to.equal(true);\n                                    done();\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'cloneITcloneENAndgetByLanguageEN'\n        it('cloneITcloneENAndgetByLanguageEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"IT\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"IT\");\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"EN\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body en\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                                    expect(res.body.id_project).to.equal(savedProject.id);\n                                    expect(res.body.data[1].default).to.equal(false);\n                                    expect(res.body.data[1].lang).to.equal(\"EN\");\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/labels/EN')\n                                        .auth(email, pwd)\n                                        .send()\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n                                            expect(res.body.lang).to.equal(\"EN\");\n                                            expect(res.body.default).to.equal(false);\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'cloneITAndgetByLanguageEN'\n        it('cloneITcloneENModifyENAndgetByLanguageEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"IT\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"EN\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                                    expect(res.body.id_project).to.equal(savedProject.id);\n\n\n\n                                    var modifiedEN = { \"lang\": \"EN\", \"data\": { \"LABEL_PLACEHOLDER\": \"type your message modified..\", \"LABEL_START_NW_CONV\": \"New conversation\", \"LABEL_FIRST_MSG\": \"Describe shortly your problem, you will be contacted by an agent.\", \"LABEL_FIRST_MSG_NO_AGENTS\": \"🤔 All operators are offline at the moment. You can anyway describe your problem. It will be assigned to the support team who will answer you as soon as possible.\", \"LABEL_FIRST_MSG_OPERATING_HOURS_CLOSED\": \"🤔 Our offices are closed. You can anyway describe your problem. It will be assigned to the support team who will answer you as soon as possible.\", \"LABEL_SELECT_TOPIC\": \"Select a topic\", \"LABEL_COMPLETE_FORM\": \"Complete the form to start a conversation with the next available agent.\", \"LABEL_FIELD_NAME\": \"Name\", \"LABEL_ERROR_FIELD_NAME\": \"Required field (minimum 5 characters).\", \"LABEL_FIELD_EMAIL\": \"Email\", \"LABEL_ERROR_FIELD_EMAIL\": \"Enter a valid email address.\", \"LABEL_WRITING\": \"is writing...\", \"AGENT_NOT_AVAILABLE\": \" Offline\", \"AGENT_AVAILABLE\": \" Online\", \"GUEST_LABEL\": \"Guest\", \"ALL_AGENTS_OFFLINE_LABEL\": \"All operators are offline at the moment\", \"LABEL_LOADING\": \"Loading...\", \"CALLOUT_TITLE_PLACEHOLDER\": \"Need Help?\", \"CALLOUT_MSG_PLACEHOLDER\": \"Click here and start chatting with us!\", \"CUSTOMER_SATISFACTION\": \"Customer satisfaction\", \"YOUR_OPINION_ON_OUR_CUSTOMER_SERVICE\": \"your opinion on our customer service\", \"DOWNLOAD_TRANSCRIPT\": \"Download transcript\", \"BACK\": \"Back\", \"YOUR_RATING\": \"your rating\", \"WRITE_YOUR_OPINION\": \"Write your opinion ... (optional)\", \"SUBMIT\": \"Submit\", \"THANK_YOU_FOR_YOUR_EVALUATION\": \"Thank you for your evaluation\", \"YOUR_RATING_HAS_BEEN_RECEIVED\": \"your rating has been received\", \"ALERT_LEAVE_CHAT\": \"Do you want to leave the chat?\", \"YES\": \"Yes\", \"NO\": \"No\", \"BUTTON_CLOSE_TO_ICON\": \"Minimize chat\", \"BUTTON_EDIT_PROFILE\": \"Update profile\", \"BUTTON_DOWNLOAD_TRANSCRIPT\": \"Download transcript\", \"RATE_CHAT\": \"Rate chat\", \"WELLCOME_TITLE\": \"Hi, welcome to Tiledesk 👋\", \"WELLCOME_MSG\": \"How can we help?\", \"WELLCOME\": \"Welcome\", \"OPTIONS\": \"options\", \"SOUND_OFF\": \"sound off\", \"SOUND_ON\": \"sound on\", \"LOGOUT\": \"logout\", \"CLOSE\": \"close\", \"PREV_CONVERSATIONS\": \"Your conversations\", \"YOU\": \"You\", \"SHOW_ALL_CONV\": \"show all\", \"START_A_CONVERSATION\": \"Start a conversation\", \"NO_CONVERSATION\": \"No conversation\", \"SEE_PREVIOUS\": \"see previous\", \"WAITING_TIME_FOUND\": \"The team typically replies in \", \"WAITING_TIME_NOT_FOUND\": \"The team will reply as soon as possible\", \"CLOSED\": \"CLOSED\" } };\n\n                                    chai.request(server)\n                                        .post('/' + savedProject._id + '/labels')\n                                        .auth(email, pwd)\n                                        .send(modifiedEN)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/labels/EN')\n                                                .auth(email, pwd)\n                                                .send()\n                                                .end((err, res) => {\n\n                                                    if (err) { console.error(\"err: \", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                                    expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"type your message modified..\");\n                                                    done();\n                                                });\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'cloneITAndgetByLanguageEN'\n        it('cloneITcloneENAndgetByLanguageFR', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"IT\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                            expect(res.body.id_project).to.equal(savedProject.id);\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"EN\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    // expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n                                    expect(res.body.id_project).to.equal(savedProject.id);\n\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/labels/FR')\n                                        .auth(email, pwd)\n                                        .send()\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            expect(res.body.data.LABEL_PLACEHOLDER).to.equal(\"Scrivi la tua domanda...\");\n\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n    });\n\n\n    describe('/update', () => {\n\n        // mocha test/labelRoute.js  --grep 'update'\n        it('updateENforClonedEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err\", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/')\n                                .auth(email, pwd)\n                                .send({ lang: \"EN\", default: true, data: { PIPPO: \"pippo\", PLUTO: \"pluto\" } })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err\", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.data.PIPPO).to.equal(\"pippo\");\n                                    expect(res.body.data.PLUTO).to.equal(\"pluto\");\n                                    expect(res.body.default).to.equal(true);\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/labels/')\n                                        .auth(email, pwd)\n                                        .send({})\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err\", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.data[0].lang).to.equal(\"EN\");\n                                            expect(res.body.data[0].default).to.equal(true);\n\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'update'\n        it('updateARforClonedEN', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err\", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/')\n                                .auth(email, pwd)\n                                .send({ lang: \"AR\", default: true, data: { PIPPO: \"pippo\", PLUTO: \"pluto\" } })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err\", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.data.PIPPO).to.equal(\"pippo\");\n                                    expect(res.body.data.PLUTO).to.equal(\"pluto\");\n                                    expect(res.body.default).to.equal(true);\n\n                                    chai.request(server)\n                                        .get('/' + savedProject._id + '/labels/')\n                                        .auth(email, pwd)\n                                        .send({})\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err\", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.data[0].lang).to.equal(\"EN\");\n                                            expect(res.body.data[0].default).to.equal(false);\n                                            expect(res.body.data[1].lang).to.equal(\"AR\");\n                                            expect(res.body.data[1].default).to.equal(true);\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n    });\n\n\n    describe('/patch', () => {\n\n        // mocha test/labelRoute.js  --grep 'patchdefaultENforClonedENanIT'\n        it('patchdefaultENforClonedENanIT', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n                            if (err) { console.error(\"err\", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"IT\" })\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err\", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_project).to.equal(savedProject.id);\n                                    expect(res.body.data[0].default).to.equal(true);\n                                    expect(res.body.data[0].lang).to.equal(\"EN\");\n                                    expect(res.body.data[1].default).to.equal(false);\n                                    expect(res.body.data[1].lang).to.equal(\"IT\");\n\n\n                                    chai.request(server)\n                                        .patch('/' + savedProject._id + '/labels/IT/default')\n                                        .auth(email, pwd)\n                                        .send({})\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err\", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n\n                                            expect(res.body.default).to.equal(true);\n                                            done();\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n    });\n\n\n    describe('/delete', () => {\n\n\n        // mocha test/labelRoute.js  --grep 'deletedforClonedENanIT'\n        it('deletedforClonedENanIT', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n                            if (err) { console.error(\"err\", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"IT\" })\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err\", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_project).to.equal(savedProject.id);\n                                    expect(res.body.data[0].default).to.equal(true);\n                                    expect(res.body.data[0].lang).to.equal(\"EN\");\n                                    expect(res.body.data[1].default).to.equal(false);\n                                    expect(res.body.data[1].lang).to.equal(\"IT\");\n\n\n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/labels/')\n                                        .auth(email, pwd)\n                                        .send({})\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err\", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.deletedCount).to.equal(1);\n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/labels/')\n                                                .auth(email, pwd)\n                                                .send({})\n                                                .end((err, res) => {\n                                                    if (err) { console.error(\"err\", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n                                                    res.should.have.status(200);\n                                                    expect(res.body).to.deep.equal({});\n\n\n                                                    done();\n                                                });\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n\n\n        // mocha test/labelRoute.js  --grep 'deleteITforClonedENanIT'\n        it('deleteITforClonedENanIT', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/labels/default/clone')\n                        .auth(email, pwd)\n                        .send({ lang: \"EN\" })\n                        .end((err, res) => {\n                            if (err) { console.error(\"err\", err); }\n                            if (log) { console.log(\"res.body\", res.body); }\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.id_project).to.equal(savedProject.id);\n                            expect(res.body.data[0].default).to.equal(true);\n                            expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/labels/default/clone')\n                                .auth(email, pwd)\n                                .send({ lang: \"IT\" })\n                                .end((err, res) => {\n                                    if (err) { console.error(\"err\", err); }\n                                    if (log) { console.log(\"res.body\", res.body); }\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.id_project).to.equal(savedProject.id);\n                                    expect(res.body.data[0].default).to.equal(true);\n                                    expect(res.body.data[0].lang).to.equal(\"EN\");\n                                    expect(res.body.data[1].default).to.equal(false);\n                                    expect(res.body.data[1].lang).to.equal(\"IT\");\n\n\n                                    chai.request(server)\n                                        .delete('/' + savedProject._id + '/labels/IT')\n                                        .auth(email, pwd)\n                                        .send({})\n                                        .end((err, res) => {\n                                            if (err) { console.error(\"err\", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.lang).to.equal(\"EN\");\n\n                                            chai.request(server)\n                                                .get('/' + savedProject._id + '/labels/')\n                                                .auth(email, pwd)\n                                                .send({})\n                                                .end((err, res) => {\n                                                    if (err) { console.error(\"err\", err); }\n                                                    if (log) { console.log(\"res.body\", res.body); }\n\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.data[0].lang).to.equal(\"EN\");\n\n\n                                                    done();\n\n                                                });\n                                        });\n                                });\n                        });\n                });\n            });\n        });\n    });\n\n\n\n});\n\n\n"
  },
  {
    "path": "test/labelService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nmongoose.connect(config.databasetest);\n\nvar labelService = require('../services/labelService');\nvar projectService = require(\"../services/projectService\");\nrequire('../services/mongoose-cache-fn')(mongoose);\n\ndescribe('labelService', function () {\n\n\n  it('getWithoutClonedLabel', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n\n      // get(id_project, language, key) {\n      labelService.get(savedProject._id, \"EN\", \"LABEL_PLACEHOLDER\").then(function (label) {\n\n        expect(label).to.equal(\"type your message..\");\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('getITLanguageButNotPresentInProject', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n\n      // get(id_project, language, key) {\n      labelService.get(savedProject._id, \"IT\", \"LABEL_PLACEHOLDER\").then(function (label) {\n\n        expect(label).to.equal(\"type your message..\");\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('getWrongLanguage', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n\n      // get(id_project, language, key) {\n      labelService.get(savedProject._id, \"OO\", \"LABEL_PLACEHOLDER\").then(function (label) {\n\n        expect(label).to.equal(\"type your message..\");\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('getLanguage', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n\n      // getLanguage(id_project, language) {\n      labelService.getLanguage(savedProject._id, \"EN\").then(function (labels) {\n\n        expect(labels.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('getLanguageWrongLang', function (done) {\n    var userid = \"5badfe5d553d1844ad654072\";\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n\n      // getLanguage(id_project, language) {\n      labelService.getLanguage(savedProject._id, \"XX\").then(function (labels) {\n\n        expect(labels.data.LABEL_PLACEHOLDER).to.equal(\"type your message..\");\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n});\n\n\n"
  },
  {
    "path": "test/leadRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nconst userService = require('../services/userService');\nconst projectService = require('../services/projectService');\nconst leadService = require('../services/leadService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n\nlet log = false;\n\nchai.use(chaiHttp);\n\n\ndescribe('LeadRoute', () => {\n\n    // mocha test/leadRoute.js  --grep 'add-tags-to-lead'\n    it('add-tags-to-lead', function (done) {\n        \n        var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n        var userid = \"5badfe5d553d1844ad654072\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n                var attr = { myprop: 123 };\n                leadService.create(\"fullname\", \"email@email.com\", savedProject._id, userid, attr).then(function (savedLead) {\n\n                    savedLead.should.have.property('_id').not.eql(null);\n                    let lead_id = savedLead._id;\n                    \n                    let tags = [ \"tag1\", \"tag2\" ]\n                    \n                    // First Step: add 2 tags on a conversation no tagged at all\n                    chai.request(server)\n                        .put('/' + savedProject._id + '/leads/' + lead_id + '/tag')\n                        .auth(email, pwd)\n                        .send(tags)\n                        .end((err, res) => {\n\n                            if (err) { console.log(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.tags).to.have.length(2);\n                            expect(res.body.tags[0]).to.equal('tag1');\n                            expect(res.body.tags[1]).to.equal('tag2');\n\n                            let tags2 = [ \"tag2\", \"tag3\" ]\n\n                            // Second Step: add more 2 tags of which one already existant in the conversation\n                            chai.request(server)\n                                .put('/' + savedProject._id + '/leads/' + lead_id + '/tag')\n                                .auth(email, pwd)\n                                .send(tags2)\n                                .end((err, res) => {\n\n                                    if (err) { console.log(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.tags).to.have.length(3);\n                                    expect(res.body.tags[0]).to.equal('tag1');\n                                    expect(res.body.tags[1]).to.equal('tag2');\n                                    expect(res.body.tags[2]).to.equal('tag3');\n\n                                    done();\n                                })\n\n                        })\n\n                }).catch(function (err) {\n                    winston.error(\"test reject\", err);\n                    assert.isNotOk(err, 'Promise error');\n                    done();\n                });\n            })\n\n        })\n\n    }).timeout(5000)\n\n    // mocha test/leadRoute.js  --grep 'remove-tags-from-lead'\n    it('remove-tags-from-lead', function (done) {\n        \n        var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n        var userid = \"5badfe5d553d1844ad654072\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n                var attr = { myprop: 123 };\n                leadService.create(\"fullname\", \"email@email.com\", savedProject._id, userid, attr).then(function (savedLead) {\n\n                    savedLead.should.have.property('_id').not.eql(null);\n                    let lead_id = savedLead._id;\n                    \n                    let tags = [ \"tag1\", \"tag2\" ]\n                    \n                    // First Step: add 2 tags on a conversation no tagged at all\n                    chai.request(server)\n                        .put('/' + savedProject._id + '/leads/' + lead_id + '/tag')\n                        .auth(email, pwd)\n                        .send(tags)\n                        .end((err, res) => {\n\n                            if (err) { console.log(\"err: \", err) };\n                            if (log) { console.log(\"res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.tags).to.have.length(2);\n                            expect(res.body.tags[0]).to.equal('tag1');\n                            expect(res.body.tags[1]).to.equal('tag2');\n\n                            let tag_to_remove = res.body.tags[1];\n\n                            chai.request(server)\n                                .delete('/' + savedProject._id + '/leads/' + lead_id + '/tag/' + tag_to_remove)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.log(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.tags).to.have.length(1);\n                                    expect(res.body.tags[0]).to.equal('tag1');\n\n                                    done();\n\n                                })\n\n                        })\n\n                }).catch(function (err) {\n                    winston.error(\"test reject\", err);\n                    assert.isNotOk(err, 'Promise error');\n                    done();\n                });\n            })\n\n        })\n\n    }).timeout(5000)\n})"
  },
  {
    "path": "test/leadService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nmongoose.connect(config.databasetest);\n\nvar leadService = require('../services/leadService');\nvar projectService = require(\"../services/projectService\");\n\nlet log = false;\n\ndescribe('LeadService()', function () {\n\n  var userid = \"5badfe5d553d1844ad654072\";\n\n  it('create', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      var attr = { myprop: 123 };\n      leadService.create(\"fullname\", \"email@email.com\", savedProject._id, userid, attr).then(function (savedLead) {\n        winston.debug(\"resolve\", savedLead.toObject());\n        expect(savedLead.fullname).to.equal(\"fullname\");\n        expect(savedLead.email).to.equal(\"email@email.com\");\n        expect(savedLead.id_project).to.equal(savedProject._id.toString());\n        expect(savedLead.lead_id).to.not.equal(null);\n        expect(savedLead.attributes).to.equal(attr);\n        expect(savedLead.attributes.myprop).to.equal(123);\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('update', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      var attr = { myprop: 123 };\n      leadService.create(\"fullname\", \"email@email.com\", savedProject._id, userid, attr).then(function (savedLead) {\n        winston.debug(\"resolve\", savedLead.toObject());\n        expect(savedLead.fullname).to.equal(\"fullname\");\n        expect(savedLead.email).to.equal(\"email@email.com\");\n        expect(savedLead.id_project).to.equal(savedProject._id.toString());\n        expect(savedLead.lead_id).to.not.equal(null);\n        expect(savedLead.attributes).to.equal(attr);\n        expect(savedLead.attributes.myprop).to.equal(123);\n\n        //  updateWitId(lead_id, fullname, email, id_project) {\n\n        leadService.updateWitId(savedLead.lead_id, \"fullname2\", \"email2@email2.com\", savedProject._id).then(function (updatedLead) {\n\n          expect(updatedLead.fullname).to.equal(\"fullname2\");\n          expect(updatedLead.email).to.equal(\"email2@email2.com\");\n          expect(updatedLead.id_project).to.equal(savedProject._id.toString());\n          expect(updatedLead.lead_id).to.not.equal(savedLead.id);\n\n          done();\n        }).catch(function (err) {\n          winston.error(\"test reject\", err);\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n      });\n    });\n  });\n\n\n  it('createWithoutEmail', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      leadService.create(\"fullname\", null, savedProject._id, userid).then(function (savedLead) {\n        winston.debug(\"resolve\", savedLead.toObject());\n        expect(savedLead.fullname).to.equal(\"fullname\");\n        expect(savedLead.email).to.equal(null);\n        expect(savedLead.id_project).to.equal(savedProject._id.toString());\n        expect(savedLead.lead_id).to.not.equal(null);\n        expect(savedLead.attributes).to.equal(undefined);\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n    });\n  });\n\n\n  it('createIfNotExists-already-exists', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      leadService.create(\"fullname\", \"email@email.com\", savedProject._id, userid).then(function (savedLead) {\n        if (log) { console.log(\"savedLead\", savedLead); }\n        leadService.createIfNotExists(\"fullname\", \"email@email.com\", savedProject._id, userid).then(function (savedLeadIfNotExists) {\n          if (log) { console.log(\"savedLeadIfNotExists\", savedLeadIfNotExists); }\n          expect(savedLead.fullname).to.equal(\"fullname\");\n          expect(savedLead.email).to.equal(\"email@email.com\");\n          expect(savedLead.id_project).to.equal(savedProject._id.toString());\n          expect(savedLeadIfNotExists._id.toString()).to.equal(savedLead._id.toString());\n          expect(savedLeadIfNotExists.lead_id).to.not.equal(null);\n\n          done();\n        }).catch(function (err) {\n          winston.error(\"test reject\", err);\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n      });\n    });\n  });\n\n\n  it('createIfNotExists-not-exists', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      leadService.createIfNotExists(\"fullname2\", \"email2@email.com\", savedProject._id, userid).then(function (savedLeadIfNotExists) {\n        if (log) { console.log(\"savedLeadIfNotExists\", savedLeadIfNotExists); }\n        expect(savedLeadIfNotExists.fullname).to.equal(\"fullname2\");\n        expect(savedLeadIfNotExists.email).to.equal(\"email2@email.com\");\n        expect(savedLeadIfNotExists.id_project).to.equal(savedProject._id.toString());\n        expect(savedLeadIfNotExists.lead_id).to.not.equal(null);\n\n        done();\n      }).catch(function (err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err, 'Promise error');\n        done();\n      });\n\n\n    });\n  });\n\n\n  it('createIfNotExistsWithId-already-exists', function (done) {\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(fullname, email, id_project, createdBy)\n      var lead_id = \"lead_id_\" + savedProject._id;\n      leadService.createWitId(lead_id, \"fullname\", \"email@email.com\", savedProject._id, userid).then(function (savedLead) {\n        if (log) { console.log(\"savedLead\", savedLead); }\n        leadService.createIfNotExistsWithLeadId(lead_id, \"fullname\", \"email@email.com\", savedProject._id, userid).then(function (savedLeadIfNotExists) {\n          if (log) { console.log(\"savedLeadIfNotExists\", savedLeadIfNotExists); }\n          expect(savedLead.fullname).to.equal(\"fullname\");\n          expect(savedLead.email).to.equal(\"email@email.com\");\n          expect(savedLead.lead_id).to.equal(lead_id);\n          expect(savedLead.id_project).to.equal(savedProject._id.toString());\n          expect(savedLeadIfNotExists._id.toString()).to.equal(savedLead._id.toString());\n          expect(savedLeadIfNotExists.lead_id).to.equal(lead_id);\n\n          done();\n        }).catch(function (err) {\n          winston.error(\"test reject\", err);\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n      });\n    });\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/logsRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nchai.use(chaiHttp);\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nlet log = false;\n\nlet mock_log = {\n    json_message: {\n        messaging_product: \"whatsapp\",\n        to: \"+393484511111\",\n        type: \"template\",\n        template: {\n            name: \"codice_sconto\",\n            language: {\n                code: \"it\"\n            },\n            components: [\n                {\n                    type: \"body\",\n                    parameters: [\n                        {\n                            \"type\": \"text\",\n                            \"text\": \"Giovanni\"\n                        }\n                    ]\n                }\n            ]\n        }\n    },\n    id_project: null,\n    transaction_id: null,\n    message_id: \"wamid.HBgMMzkzNDg0NTA2NjI3FQIAERgSQTRDNzRDOTM3NzA5Mjk3NzJFAA==\",\n    status: \"read\",\n    status_code: 3,\n    error: null,\n}\n\ndescribe('LogsRoute', () => {\n\n    describe('/getlogs', () => {\n\n        it('whatsapp', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n                projectService.create(\"test1\", savedUser._id).then(function (savedProject) {\n\n                    mock_log.id_project = savedProject._id;\n                    mock_log.transaction_id = \"automation-request-\" + savedProject._id;\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/logs/whatsapp')\n                        .auth(email, pwd)\n                        .send(mock_log)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body); }\n                            \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            chai.request(server)\n                                .get('/' + savedProject._id + '/logs/whatsapp/' + mock_log.transaction_id)\n                                .auth(email, pwd)\n                                .end((err, res) => {\n                                    \n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"res.body: \", res.body); }\n\n                                    res.should.have.status(200);\n                                    \n                                    done();\n\n                                })\n\n                        })\n\n                });\n            });\n\n        });\n\n\n    });\n});\n\n"
  },
  {
    "path": "test/messageRootRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\nvar messageService = require('../services/messageService');\nvar Project_user = require(\"../models/project_user\");\nvar roleConstants = require('../models/roleConstants');\nconst uuidv4 = require('uuid/v4');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nlet log = false;\n\nchai.use(chaiHttp);\n\ndescribe('MessageRoute', () => {\n\n\n  // mocha test/messageRootRoute.js  --grep 'createSimple'\n\n  it('createSimple', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      var email2 = \"test-message-create-\" + Date.now() + \"@email.com\";\n      userService.signup(email2, pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n\n        projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n          var savedProject = savedProjectAndPU.project;\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/messages')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({ \"recipient\": savedUser2._id.toString(), \"recipientFullname\": \"Dest\", \"text\": \"text\" })\n            .end(function (err, res) {\n\n              if (err) { console.error(\"err: \", err); }\n              if (log) { console.log(\"res.body: \", res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n\n              expect(res.body.sender).to.equal(savedUser._id.toString());\n              expect(res.body.senderFullname).to.equal(\"Test Firstname Test lastname\");\n              expect(res.body.recipient).to.equal(savedUser2._id.toString());\n              expect(res.body.type).to.equal(\"text\");\n              expect(res.body.text).to.equal(\"text\");\n              expect(res.body.id_project).to.equal(savedProject._id.toString());\n              expect(res.body.createdBy).to.equal(savedUser._id.toString());\n              expect(res.body.status).to.equal(0);\n              expect(res.body.request).to.equal(undefined);\n              expect(res.body.channel_type).to.equal(\"direct\");\n              expect(res.body.channel.name).to.equal(\"chat21\");\n\n\n              done();\n            });\n        });\n      });\n    });\n  });\n\n\n\n\n\n  // mocha test/messageRootRoute.js  --grep 'createValidationNoRecipient'\n  it('createValidationNoRecipient', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\" })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body: \", res.body); }\n\n            res.should.have.status(422);\n            res.body.should.be.a('object');\n\n            done();\n          });\n      });\n    });\n  });\n\n\n\n\n  // mocha test/messageRootRoute.js  --grep 'createValidationNoText'\n  it('createValidationNoText', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"recipient\": \"5ddd30bff0195f0017f72c6d\", \"recipientFullname\": \"Dest\" })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body: \", res.body); }\n\n            res.should.have.status(422);\n            res.body.should.be.a('object');\n\n            done();\n          });\n      });\n    });\n  });\n\n\n\n\n  // mocha test/messageRootRoute.js  --grep 'createWithSenderFullName'\n\n  it('createWithSenderFullName', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"senderFullname\": \"Pippo\", \"recipient\": \"5ddd30bff0195f0017f72c6d\", \"recipientFullname\": \"Dest\", \"text\": \"text\" })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body: \", res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            expect(res.body.senderFullname).to.equal(\"Pippo\");\n            expect(res.body.recipient).to.equal(\"5ddd30bff0195f0017f72c6d\");\n            expect(res.body.type).to.equal(\"text\");\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request).to.equal(undefined);\n            expect(res.body.channel_type).to.equal(\"direct\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n\n\n            done();\n          });\n      });\n    });\n  });\n\n\n});\n\n\n"
  },
  {
    "path": "test/messageRoute-newjwt._js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\nvar messageService = require('../services/messageService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('MessageRoute', () => {\n\n\n\n  it('create', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n     projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function(savedProjectAndPU) {\n     \n      var savedProject = savedProjectAndPU.project;\n\n          chai.request(server)\n            .post('/'+ savedProject._id + '/requests/req123/messages')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({\"text\":\"text\"})\n            .end(function(err, res) {\n                //console.log(\"res\",  res);\n                console.log(\"res.body\",  res.body);\n                res.should.have.status(200);\n                res.body.should.be.a('object');                          \n\n                expect(res.body.sender).to.equal(savedUser._id.toString());\n                // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                expect(res.body.recipient).to.equal(\"req123\");\n                expect(res.body.text).to.equal(\"text\");\n                expect(res.body.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.createdBy).to.equal(savedUser._id.toString());\n                expect(res.body.status).to.equal(0);\n\n                expect(res.body.request.request_id).to.equal(\"req123\");\n                // expect(res.body.request.requester_id).to.equal(\"sender\");\n                expect(res.body.request.first_text).to.equal(\"text\");\n                expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n                expect(res.body.request.messages_count).to.equal(1);\n                expect(res.body.request.status).to.equal(200);                                \n                expect(res.body.request.agents.length).to.equal(1);\n                expect(res.body.request.participants.length).to.equal(1);\n                expect(res.body.request.department).to.not.equal(null);\n                expect(res.body.request.lead).to.not.equal(null);               \n                            \n          \n               done();\n            });\n    });\n  });\n});\n\n\n\n\n\nit('getall', function (done) {\n  // this.timeout(10000);\n\n  var email = \"test-ssa-\" + Date.now() + \"@email.com\";\n  var pwd = \"pwd\";\n\n  userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n    projectService.create(\"message-create\", savedUser._id).then(function(savedProject) {\n\n      leadService.createIfNotExists(\"leadfullname-message-getall\", \"andrea.leo@-subscription-message-getall.it\", savedProject._id).then(function(createdLead) {\n        requestService.createWithId(\"request_id-message-getall\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            messageService.create(savedUser._id, \"senderFullname\", savedRequest.request_id, \"hello\",\n            savedProject._id, savedUser._id).then(function(savedMessage){\n                expect(savedMessage.text).to.equal(\"hello\");     \n\n\n\n                      chai.request(server)\n                  .get('/'+ savedProject._id + '/requests/request_id-message-getall/messages')\n                  .auth(email, pwd)\n                  .set('content-type', 'application/json')\n                  .end(function(err, res) {\n                      //console.log(\"res\",  res);\n                      console.log(\"res.body\",  res.body);\n                      res.should.have.status(200);\n                      res.body.should.be.a('array');                          \n\n                      expect(res.body[0].sender).to.equal(savedUser._id.toString());\n                      expect(res.body[0].senderFullname).to.equal(\"senderFullname\");\n                      expect(res.body[0].recipient).to.equal(\"request_id-message-getall\");\n                      expect(res.body[0].text).to.equal(\"hello\");\n                      expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                      expect(res.body[0].createdBy).to.equal(savedUser._id.toString());\n                      expect(res.body[0].status).to.equal(200);\n\n                      // expect(res.body.request.request_id).to.equal(\"req123\");\n                      // expect(res.body.request.requester_id).to.equal(\"sender\");\n                      // expect(res.body.request.first_text).to.equal(\"text\");\n                      // expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                      // expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n                      // expect(res.body.request.messages_count).to.equal(1);\n                      // expect(res.body.request.status).to.equal(200);                                \n                      // expect(res.body.request.agents.length).to.equal(1);\n                      // expect(res.body.request.participants.length).to.equal(1);\n                      // expect(res.body.request.department).to.not.equal(null);\n                      // expect(res.body.request.lead).to.equal(null);               \n                                  \n                \n                    done();\n                  });\n\n                  \n                });\n            });\n        });\n    });\n  });\n     \n\n\n});\n\n\n\ndescribe('/SendMessageSigninWithCustomToken', () => {\n \n\n  it('sendMessageSigninWithCustomTokenOk', (done) => {\n\n      \n      var email = \"test-sendMessageSigninWithCustomTokenOk-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n          // create(name, createdBy, settings)\n          projectService.create(\"test-sendMessageSigninWithCustomTokenOk\", savedUser._id).then(function(savedProject) {     \n        \n              chai.request(server)\n              .post('/'+ savedProject._id + '/keys/generate')\n              .auth(email, pwd)\n              .send()\n              .end((err, res) => {\n                  //console.log(\"res\",  res);\n                  console.log(\"res.body\",  res.body);\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.jwtSecret).to.not.equal(null);                                                                              \n              \n                  // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n                  var externalUserId = \"123\";\n                  var externalUserObj = {_id: externalUserId, firstname:\"andrea\", lastname:\"leo\", email: \"email2@email.com\"};\n                  \n                  console.log(\"externalUserObj\", externalUserObj);\n\n// attento qui\n                  var signOptions = {                                                            \n                      subject:  'userexternal',                                                                 \n                      audience:  '/projects/'+savedProject._id ,                                              \n                      // audience:  'https://tiledesk.com/projects/'+savedProject._id ,                                              \n                      };\n\n\n                  var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret,signOptions);\n              \n                  console.log(\"jwtToken\", jwtToken);\n\n\n                  chai.request(server)\n                      .post('/auth/signinWithCustomToken' )\n                      .set('Authorization', 'JWT '+jwtToken)\n                      //.send({ id_project: savedProject._id})\n                      .send()\n                      .end((err, res) => {\n                          //console.log(\"res\",  res);\n                          console.log(\"res.body\",  res.body);\n                          res.should.have.status(200);\n                          res.body.should.be.a('object');\n                          expect(res.body.success).to.equal(true);                                                                                                                     \n                          expect(res.body.user.email).to.equal(\"email2@email.com\");  \n                          expect(res.body.user.firstname).to.equal(\"andrea\");                                               \n                         \n                          expect(res.body.token).to.not.equal(undefined);  \n                          expect(res.body.token).to.equal('JWT '+jwtToken);  \n                                                                       \n                      \n                          chai.request(server)\n                              .post('/'+ savedProject._id + '/requests/sendMessageSigninWithCustomTokenOk/messages')\n                              .set('Authorization', 'JWT '+jwtToken)\n                              .set('content-type', 'application/json')\n                              .send({\"text\":\"text\"})\n                              .end(function(err, res) {\n                                  //console.log(\"res\",  res);\n                                  console.log(\"res.body\",  res.body);\n                                  res.should.have.status(200);\n                                  res.body.should.be.a('object');                          \n\n                                  expect(res.body.sender).to.equal(externalUserId);\n                                  // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                                  // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                                  expect(res.body.recipient).to.equal(\"sendMessageSigninWithCustomTokenOk\");\n                                  expect(res.body.text).to.equal(\"text\");\n                                  expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                  expect(res.body.createdBy).to.equal(externalUserId);\n                                  expect(res.body.status).to.equal(0);\n\n                                  expect(res.body.request.request_id).to.equal(\"sendMessageSigninWithCustomTokenOk\");\n                                  expect(res.body.request.first_text).to.equal(\"text\");\n                                  expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                                  expect(res.body.request.createdBy).to.equal(externalUserId);\n                                  expect(res.body.request.messages_count).to.equal(1);\n                                  expect(res.body.request.status).to.equal(200);                                \n                                  expect(res.body.request.agents.length).to.equal(1);\n                                  expect(res.body.request.participants.length).to.equal(1);\n                                  expect(res.body.request.department).to.not.equal(null);\n                                  expect(res.body.request.lead).to.not.equal(null);               \n                                 \n                            \n                                  chai.request(server)\n                                    .get('/'+ savedProject._id + '/requests/sendMessageSigninWithCustomTokenOk')\n                                    .auth(email, pwd)\n                                    .set('content-type', 'application/json')                                   \n                                    .end(function(err, res) {\n                                        //console.log(\"res\",  res);\n                                        console.log(\"res.body\",  res.body);\n                                        expect(res.body.lead.lead_id).to.equal(externalUserId);\n                                        expect(res.body.lead.email).to.equal(\"email2@email.com\");\n                                        expect(res.body.lead.fullname).to.equal(\"andrea leo\");\n                                        expect(res.body.requester.role).to.equal(\"user\");\n                                        expect(res.body.requester.uuid_user).to.equal(externalUserId);\n                                        expect(res.body.requester.id_user).to.equal(undefined);\n                                        done()\n                                    });\n                                        \n                              });\n                      });\n                  });\n              });\n          });\n              \n  });\n\n});\n\n\n\n\n\n\n\n\ndescribe('/SendMessageSigninAnonym', () => {\n \n\n  it('sendMessageSigninAnonym', (done) => {\n\n      \n      var email = \"test-sendMessageSigninAnonym-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n          // create(name, createdBy, settings)\n          projectService.create(\"test-sendMessageSigninAnonym\", savedUser._id).then(function(savedProject) {     \n        \n             \n\n                  chai.request(server)\n                      .post('/auth/signinAnonymously' )\n                      .send({ id_project: savedProject._id})\n                      .end((err, res) => {\n                          //console.log(\"res\",  res);\n                          console.log(\"res.body\",  res.body);\n                          res.should.have.status(200);\n                          res.body.should.be.a('object');\n                          var userId = res.body.user._id;\n                          expect(res.body.success).to.equal(true);                                                                                                                     \n                          expect(res.body.user.email).to.equal(undefined);  \n                          expect(res.body.user.firstname).to.equal(\"Guest\");                                               \n                         \n                          expect(res.body.token).to.not.equal(undefined);  \n                                                                       \n                      \n                          chai.request(server)\n                              .post('/'+ savedProject._id + '/requests/sendMessageSigninAnonym/messages')\n                              .set('Authorization', res.body.token)\n                              .set('content-type', 'application/json')\n                              .send({\"text\":\"text\"})\n                              .end(function(err, res) {\n                                  //console.log(\"res\",  res);\n                                  console.log(\"res.body\",  res.body);\n                                  res.should.have.status(200);\n                                  res.body.should.be.a('object');                          \n\n                                  expect(res.body.sender).to.equal(userId);\n                                  // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                                  // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                                  expect(res.body.recipient).to.equal(\"sendMessageSigninAnonym\");\n                                  expect(res.body.text).to.equal(\"text\");\n                                  expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                  expect(res.body.createdBy).to.equal(userId);\n                                  expect(res.body.status).to.equal(0);\n\n                                  expect(res.body.request.request_id).to.equal(\"sendMessageSigninAnonym\");\n                                  expect(res.body.request.first_text).to.equal(\"text\");\n                                  expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                                  expect(res.body.request.createdBy).to.equal(userId);\n                                  expect(res.body.request.messages_count).to.equal(1);\n                                  expect(res.body.request.status).to.equal(200);                                \n                                  expect(res.body.request.agents.length).to.equal(1);\n                                  expect(res.body.request.participants.length).to.equal(1);\n                                  expect(res.body.request.department).to.not.equal(null);\n                                  expect(res.body.request.lead).to.not.equal(null);               \n                                 \n                            \n                                  chai.request(server)\n                                    .get('/'+ savedProject._id + '/requests/sendMessageSigninAnonym')\n                                    .auth(email, pwd)\n                                    .set('content-type', 'application/json')                                   \n                                    .end(function(err, res) {\n                                        //console.log(\"res\",  res);\n                                        console.log(\"res.body\",  res.body);\n                                        expect(res.body.lead.lead_id).to.equal(userId);\n                                        expect(res.body.lead.email).to.equal(undefined);\n                                        expect(res.body.lead.fullname).to.equal(\"Guest \");\n                                        expect(res.body.requester.role).to.equal(\"guest\");\n                                        expect(res.body.requester.uuid_user).to.equal(userId);\n                                        expect(res.body.requester.id_user).to.equal(undefined);\n                                        done()\n                                    });\n                                        \n                              });\n                      });\n              \n              });\n          });\n              \n  });\n\n});\n\n});\n\n\n"
  },
  {
    "path": "test/messageRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar User = require('../models/user');\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\nvar messageService = require('../services/messageService');\nvar Project_user = require(\"../models/project_user\");\nvar RoleConstants = require('../models/roleConstants');\nconst uuidv4 = require('uuid/v4');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar winston = require('../config/winston');\nvar jwt = require('jsonwebtoken');\n// chai.config.includeStack = true;\nlet log = false;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('MessageRoute', () => {\n\n  // mocha test/messageRoute.js  --grep 'createSimple'\n  it('createSimple', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\" })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(null);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n\n            done();\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createSimpleEmptyText'\n  it('createSimpleEmptyText', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"\" })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(422);\n\n            done();\n          });\n      });\n    });\n  }).timeout(20000);\n\n  // mocha test/messageRoute.js  --grep 'createSimpleNoText'\n  it('createSimpleNoText', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({})\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(422);\n\n            done();\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createSimpleWithAttributes'\n  it('createSimpleWithAttributes', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req-createSimpleWithAttributes-\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\", \"attributes\": { \"a\": \"b\" } })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.attributes.a).to.equal(\"b\");\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(null);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.request.attributes.a).to.equal(\"b\");\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createWithSender'\n  it('createWithSender', function (done) {\n\n    var email = \"test-message-createwithsender-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      var email2 = \"test-message-createwithsender22-\" + Date.now() + \"@email.com\";\n      var pwd2 = \"pwd\";\n\n      userService.signup(email2, pwd2, \"Test Firstname22\", \"Test lastname22\").then(function (savedUser2) {\n        projectService.createAndReturnProjectAndProjectUser(\"message-createwithsender\", savedUser._id).then(function (savedProjectAndPU) {\n\n          var savedProject = savedProjectAndPU.project;\n\n          var pu2 = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: savedProject._id,\n            id_user: savedUser2._id,\n            role: RoleConstants.AGENT,\n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: true,\n            createdBy: savedUser2._id,\n            updatedBy: savedUser2._id,\n          });\n          pu2.save(function (err, savedProject_user2) {\n\n            let request_id = \"req-createwithsender-\" + Date.now();\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send({ \"text\": \"text\", \"sender\": savedUser2._id.toString() })\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                \n                res.should.have.status(200);\n                res.body.should.be.a('object');\n                expect(res.body.sender).to.equal(savedUser2._id.toString());\n                // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                expect(res.body.senderFullname).to.equal(\"Test Firstname22 Test lastname22\");\n                expect(res.body.recipient).to.equal(request_id);\n                expect(res.body.text).to.equal(\"text\");\n                expect(res.body.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.createdBy).to.equal(savedUser._id.toString());\n                expect(res.body.status).to.equal(0);\n                expect(res.body.request.request_id).to.equal(request_id);\n                expect(res.body.request.requester._id).to.equal(savedProject_user2._id.toString());\n                expect(res.body.request.requester.id_user.email).to.equal(email2);\n                expect(res.body.request.requester.id_user.firstname).to.equal(\"Test Firstname22\");\n                expect(res.body.request.requester.id_user.lastname).to.equal(\"Test lastname22\");\n                // expect(res.body.request.requester._id).to.equal(savedProject_user2._id.toString());\n                // expect(res.body.request.requester_id).to.equal(\"sender\");\n                expect(res.body.request.first_text).to.equal(\"text\");\n                expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n                // expect(res.body.request.messages_count).to.equal(1);\n                expect(res.body.request.status).to.equal(200);\n                //expect(res.body.request.snapshot.agents.length).to.equal(2);\n                expect(res.body.request.participants.length).to.equal(1);\n                expect(res.body.request.department).to.not.equal(null);\n                expect(res.body.request.lead).to.not.equal(null);\n                expect(res.body.channel_type).to.equal(\"group\");\n                expect(res.body.channel.name).to.equal(\"chat21\");\n                expect(res.body.request.channel.name).to.equal(\"chat21\");\n                expect(res.body.request.location).to.equal(undefined);\n\n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createWithSenderFromLead'\n  it('createWithSenderFromLead', function (done) {\n\n    var email = \"test-message-createwithsenderfromlead-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createwithsender\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        var uid = uuidv4();\n        var pu = new Project_user({\n          // _id: new mongoose.Types.ObjectId(),\n          id_project: savedProject._id,\n          uuid_user: uid,\n          role: RoleConstants.USER,\n          roleType : RoleConstants.TYPE_USERS,   \n          user_available: true,\n          createdBy: savedUser._id,\n          updatedBy: savedUser._id,\n        });\n        pu.save(function (err, savedProject_user) {\n          // createIfNotExistsWithLeadId(lead_id, fullname, email, id_project, createdBy, attributes, status) {\n          leadService.createIfNotExistsWithLeadId(uid, \"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n\n            var now = Date.now();\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/req123-createwithsender-' + now + '/messages')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send({ \"text\": \"text\", \"sender\": uid })\n              .end(function (err, res) {\n                // console.log(\"res\",  res);\n                if (log) { console.log(\"res.body\", res.body); }\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                expect(res.body.sender).to.equal(uid);\n                // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                expect(res.body.senderFullname).to.equal(\"leadfullname\");\n                expect(res.body.recipient).to.equal(\"req123-createwithsender-\" + now);\n                expect(res.body.text).to.equal(\"text\");\n                expect(res.body.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.createdBy).to.equal(savedUser._id.toString());\n                expect(res.body.status).to.equal(0);\n\n                expect(res.body.request.request_id).to.equal(\"req123-createwithsender-\" + now);\n                expect(res.body.request.requester._id).to.equal(savedProject_user._id.toString());\n                expect(res.body.request.requester.uuid_user).to.equal(uid);\n                // expect(res.body.request.requester.id_user.firstname).to.equal(\"Test Firstname22\");\n                // expect(res.body.request.requester.id_user.lastname).to.equal(\"Test lastname22\");\n                // expect(res.body.request.requester._id).to.equal(savedProject_user2._id.toString());\n                // expect(res.body.request.requester_id).to.equal(\"sender\");\n                expect(res.body.request.first_text).to.equal(\"text\");\n                expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n                // expect(res.body.request.messages_count).to.equal(1);\n                expect(res.body.request.status).to.equal(200);\n                //expect(res.body.request.snapshot.agents.length).to.equal(1);\n                expect(res.body.request.participants.length).to.equal(1);\n                expect(res.body.request.department).to.not.equal(null);\n                expect(res.body.request.lead).to.not.equal(null);\n                expect(res.body.channel_type).to.equal(\"group\");\n                expect(res.body.channel.name).to.equal(\"chat21\");\n                expect(res.body.request.channel.name).to.equal(\"chat21\");\n                expect(res.body.request.location).to.equal(undefined);\n\n                done();\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createWithLocation'\n  it('createWithLocation', function (done) {\n    \n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req-createWithLocation-\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ text: \"text\", location: { country: \"Italy\", streetAddress: \"Via Roma, 767b\", ipAddress: \"192.168.1.1\", geometry: { type: \"Point\", coordinates: [-109, 41] } } })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.location.country).to.equal(\"Italy\");\n            expect(res.body.request.location.streetAddress).to.equal(\"Via Roma, 767b\");\n            expect(res.body.request.location.ipAddress).to.equal(\"192.168.1.1\");\n            expect(res.body.request.location.geometry.type).to.equal(\"Point\");\n            expect(res.body.request.location.geometry.coordinates[0]).to.equal(-109);\n            expect(res.body.request.location.geometry.coordinates[1]).to.equal(41);\n\n            done();\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createWithLocationAsAttributes'\n  it('createWithLocationAsAttributes', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req-createWithLocationAsAttributes-\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ text: \"text\", attributes: { ipAddress: \"95.255.73.34\" } })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/' + request_id)\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send()\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                \n                res.should.have.status(200);\n                res.body.should.be.a('object');\n                expect(res.body.request_id).to.equal(request_id);\n                expect(res.body.location.country).to.equal(\"IT\");\n                expect(res.body.location.ipAddress).to.equal(\"95.255.73.34\");\n                expect(res.body.location.geometry.type).to.equal(\"Point\");\n                expect(res.body.location.geometry.coordinates[0]).to.equal(42.6716);\n                expect(res.body.location.geometry.coordinates[1]).to.equal(14.0148);\n\n                done();\n              });\n          });\n      });\n    });\n  });\n\n  it('createDifferentChannel', function (done) {\n\n    var email = \"test-message-createdifferentchannel-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req-channel1-\" + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ text: \"text\", channel: { name: \"channel1\" } })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(undefined);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"channel1\");\n            expect(res.body.request.channel.name).to.equal(\"channel1\");\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createWithMessageStatus'\n  it('createWithMessageStatus', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_createWithMessageStatus-\" + Date.now();\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\", \"status\": 999 })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(999);\n            expect(res.body.request.request_id).to.equal(request_id);\n            if (log) { console.log(\"res.body.request.requester\", JSON.stringify(res.body.request.requester)); }\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(null);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n\n            done();\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createWithParticipants'\n  it('createWithParticipants', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_createWithParticipants-\" + Date.now();\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\", \"participants\": [savedUser._id] })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request.request_id).to.equal(request_id);\n            // expect(res.body.request.requester).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.participants[0]).to.equal(savedUser._id.toString());\n            if (log) { console.log(\"res.body.request.participatingAgents[0]\", JSON.stringify(res.body.request.participatingAgents[0])); }\n            expect(res.body.request.participatingAgents[0]._id).to.equal(savedUser._id.toString());\n            expect(res.body.request.department).to.equal(undefined);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createWithPriority'\n  it('createWithPriority', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createWithPriority\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        var reqid = 'req123-createWithPriority' + Date.now();\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + reqid + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\", \"priority\": \"hight\" })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", res.body); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(reqid);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request.request_id).to.equal(reqid);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(null);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.request.priority).to.equal(\"hight\");\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createSimpleWithFollowers'\n  it('createSimpleWithFollowers', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        let request_id = \"req_createSimpleWithFollowers-\" + Date.now();\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"text\": \"text\", \"followers\": [savedProjectAndPU.project_user._id.toString()] })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err\", err); }\n            if (log) { console.log(\"res.body\", JSON.stringify(res.body)); }\n            \n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            expect(res.body.sender).to.equal(savedUser._id.toString());\n            // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n            expect(res.body.recipient).to.equal(request_id);\n            expect(res.body.text).to.equal(\"text\");\n            expect(res.body.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.createdBy).to.equal(savedUser._id.toString());\n            expect(res.body.status).to.equal(0);\n            expect(res.body.request.request_id).to.equal(request_id);\n            expect(res.body.request.requester._id).to.equal(savedProjectAndPU.project_user._id.toString());\n            // expect(res.body.request.requester_id).to.equal(\"sender\");\n            expect(res.body.request.first_text).to.equal(\"text\");\n            expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n            expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n            // expect(res.body.request.messages_count).to.equal(1);\n            expect(res.body.request.status).to.equal(200);\n            //expect(res.body.request.snapshot.agents.length).to.equal(1);\n            expect(res.body.request.participants.length).to.equal(1);\n            expect(res.body.request.department).to.not.equal(null);\n            expect(res.body.request.lead).to.not.equal(null);\n            expect(res.body.channel_type).to.equal(\"group\");\n            expect(res.body.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.channel.name).to.equal(\"chat21\");\n            expect(res.body.request.location).to.equal(undefined);\n            expect(res.body.request.followers[0]).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            done();\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createMultiTextNoSender1'\n  it('createMultiTextNoSender1', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createMultiText\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (log) { console.log(\"res.body\", res.body); }\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('first_text').eql('first_text');\n\n            var request_id = res.body.request_id;\n            if (log) { console.log(\"request_id\", request_id); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/' + request_id + '/messages/multi')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send([{ \"text\": \"text1\" }, { \"text\": \"text2\" }])\n              .end(function (err, res) {\n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                res.should.have.status(200);\n                res.body.should.be.a('array');\n\n                expect(res.body[0].sender).to.equal(savedUser._id.toString());\n                expect(res.body[0].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                expect(res.body[0].recipient).to.equal(request_id);\n                expect(res.body[0].text).to.equal(\"text1\");\n                expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[0].createdBy).to.equal(savedUser._id.toString());\n                expect(res.body[0].status).to.equal(0);\n                expect(res.body[0].channel_type).to.equal(\"group\");\n                expect(res.body[0].channel.name).to.equal(\"chat21\");\n                expect(res.body[1].sender).to.equal(savedUser._id.toString());\n                expect(res.body[0].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                expect(res.body[1].recipient).to.equal(request_id);\n                expect(res.body[1].text).to.equal(\"text2\");\n                expect(res.body[1].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[1].createdBy).to.equal(savedUser._id.toString());\n                expect(res.body[1].status).to.equal(0);\n                expect(res.body[1].channel_type).to.equal(\"group\");\n                expect(res.body[1].channel.name).to.equal(\"chat21\");\n\n                done();\n              });\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createMultiTextNoSenderNoText'\n  it('createMultiTextNoSenderNoText', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createMultiText\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (log) { console.log(\"res.body\", res.body); }\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('first_text').eql('first_text');\n\n            var request_id = res.body.request_id;\n            if (log) { console.log(\"request_id\", request_id); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/' + request_id + '/messages/multi')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send([{}, { \"text\": \"text2\" }])\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                \n                res.should.have.status(200);\n                done();\n              });\n          });\n      });\n    });\n  });\n\n  // mocha test/messageRoute.js  --grep 'createMultiTextWithSender'\n  it('createMultiTextWithSender', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      var email2 = \"test-message-createwithsender22-\" + Date.now() + \"@email.com\";\n      var pwd2 = \"pwd\";\n\n      userService.signup(email2, pwd2, \"Test Firstname22\", \"Test lastname22\").then(function (savedUser2) {\n        projectService.createAndReturnProjectAndProjectUser(\"message-createMultiText\", savedUser._id).then(function (savedProjectAndPU) {\n\n          var savedProject = savedProjectAndPU.project;\n\n          var pu2 = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: savedProject._id,\n            id_user: savedUser2._id,\n            role: RoleConstants.USER,\n            roleType : RoleConstants.TYPE_USERS,   \n            user_available: true,\n            createdBy: savedUser2._id,\n            updatedBy: savedUser2._id,\n          });\n          pu2.save(function (err, savedProject_user2) {\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send({ \"first_text\": \"first_text\" })\n              .end(function (err, res) {\n\n                if (log) { console.log(\"res.body\", res.body); }\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                res.body.should.have.property('first_text').eql('first_text');\n\n                var request_id = res.body.request_id;\n                if (log) { console.log(\"request_id\", request_id); }\n\n                chai.request(server)\n                  .post('/' + savedProject._id + '/requests/' + request_id + '/messages/multi')\n                  .auth(email, pwd)\n                  .set('content-type', 'application/json')\n                  .send([{ \"sender\": savedUser2._id, \"text\": \"text1\" }, { \"sender\": savedUser2._id, \"text\": \"text2\" }])\n                  .end(function (err, res) {\n                    \n                    if (err) { console.error(\"err\", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n                    \n                    res.should.have.status(200);\n                    res.body.should.be.a('array');\n                    expect(res.body[0].sender).to.equal(savedUser2._id.toString());\n                    expect(res.body[0].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                    expect(res.body[0].recipient).to.equal(request_id);\n                    expect(res.body[0].text).to.equal(\"text1\");\n                    expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                    expect(res.body[0].createdBy).to.equal(savedUser2._id.toString());\n                    expect(res.body[0].status).to.equal(0);\n                    expect(res.body[0].channel_type).to.equal(\"group\");\n                    expect(res.body[0].channel.name).to.equal(\"chat21\");\n                    expect(res.body[1].sender).to.equal(savedUser2._id.toString());\n                    expect(res.body[1].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                    expect(res.body[1].recipient).to.equal(request_id);\n                    expect(res.body[1].text).to.equal(\"text2\");\n                    expect(res.body[1].id_project).to.equal(savedProject._id.toString());\n                    expect(res.body[1].createdBy).to.equal(savedUser2._id.toString());\n                    expect(res.body[1].status).to.equal(0);\n                    expect(res.body[1].channel_type).to.equal(\"group\");\n                    expect(res.body[1].channel.name).to.equal(\"chat21\");\n\n                    done();\n                  });\n              });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createMultiTextWithHardcodedSender'\n  it('createMultiTextWithHardcodedSender', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createMultiTextWithHardcodedSender\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (log) { console.log(\"res.body\", res.body); }\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('first_text').eql('first_text');\n\n            var request_id = res.body.request_id;\n            if (log) { console.log(\"request_id\", request_id); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/' + request_id + '/messages/multi')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send([{ \"sender\": \"andrealeo\", \"text\": \"text1\" }, { \"sender\": \"rocco\", \"text\": \"text2\" }])\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                res.should.have.status(200);\n                res.body.should.be.a('array');\n\n                expect(res.body[0].sender).to.equal(\"andrealeo\");\n                expect(res.body[0].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                expect(res.body[0].recipient).to.equal(request_id);\n                expect(res.body[0].text).to.equal(\"text1\");\n                expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[0].createdBy).to.equal(\"andrealeo\");\n                expect(res.body[0].status).to.equal(0);\n                expect(res.body[0].channel_type).to.equal(\"group\");\n                expect(res.body[0].channel.name).to.equal(\"chat21\");\n                expect(res.body[1].sender).to.equal(\"rocco\");\n                expect(res.body[1].senderFullname).to.equal(\"Test Firstname Test lastname\");\n                expect(res.body[1].recipient).to.equal(request_id);\n                expect(res.body[1].text).to.equal(\"text2\");\n                expect(res.body[1].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[1].createdBy).to.equal(\"rocco\");\n                expect(res.body[1].status).to.equal(0);\n                expect(res.body[1].channel_type).to.equal(\"group\");\n                expect(res.body[1].channel.name).to.equal(\"chat21\");\n\n                done();\n              });\n          });\n      });\n    });\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'createMultiTextWithHardcodedSenderAndSenderFullname'\n  it('createMultiTextWithHardcodedSenderAndSenderFullname', function (done) {\n\n    var email = \"test-message-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-createMultiTextWithHardcodedSenderAndSenderFullname\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (log) { console.log(\"res.body\", res.body); }\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            res.body.should.have.property('first_text').eql('first_text');\n\n            var request_id = res.body.request_id;\n            if (log) { console.log(\"request_id\", request_id); }\n\n            chai.request(server)\n              .post('/' + savedProject._id + '/requests/' + request_id + '/messages/multi')\n              .auth(email, pwd)\n              .set('content-type', 'application/json')\n              .send([{ \"sender\": \"andrealeo\", \"senderFullname\": \"Andrea\", \"text\": \"text1\" }, { \"sender\": \"rocco\", \"senderFullname\": \"Rocco\", \"text\": \"text2\" }])\n              .end(function (err, res) {\n                if (err) { console.error(\"err\", err); }\n                if (log) { console.log(\"res.body\", res.body); }\n                res.should.have.status(200);\n                res.body.should.be.a('array');\n\n                expect(res.body[0].sender).to.equal(\"andrealeo\");\n                expect(res.body[0].senderFullname).to.equal(\"Andrea\");\n                expect(res.body[0].recipient).to.equal(request_id);\n                expect(res.body[0].text).to.equal(\"text1\");\n                expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[0].createdBy).to.equal(\"andrealeo\");\n                expect(res.body[0].status).to.equal(0);\n                expect(res.body[0].channel_type).to.equal(\"group\");\n                expect(res.body[0].channel.name).to.equal(\"chat21\");\n                expect(res.body[1].sender).to.equal(\"rocco\");\n                expect(res.body[1].senderFullname).to.equal(\"Rocco\");\n                expect(res.body[1].recipient).to.equal(request_id);\n                expect(res.body[1].text).to.equal(\"text2\");\n                expect(res.body[1].id_project).to.equal(savedProject._id.toString());\n                expect(res.body[1].createdBy).to.equal(\"rocco\");\n                expect(res.body[1].status).to.equal(0);\n                expect(res.body[1].channel_type).to.equal(\"group\");\n                expect(res.body[1].channel.name).to.equal(\"chat21\");\n\n                done();\n              });\n          });\n      });\n    });\n  });\n\n\n  it('getall', function (done) {\n\n    var email = \"test-ssa-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      // projectService.create(\"message-create\", savedUser._id).then(function(savedProject) {\n      projectService.createAndReturnProjectAndProjectUser(\"message-create\", savedUser._id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        leadService.createIfNotExists(\"leadfullname-message-getall\", \"andrea.leo@-subscription-message-getall.it\", savedProject._id).then(function (createdLead) {\n          // requestService.createWithId(\"request_id-message-getall\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n          let request_id = \"req_message-getall-\" + Date.now();\n          requestService.createWithIdAndRequester(request_id, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n            messageService.create(savedUser._id, \"senderFullname\", savedRequest.request_id, \"hello\",\n              savedProject._id, savedUser._id).then(function (savedMessage) {\n                expect(savedMessage.text).to.equal(\"hello\");\n\n                chai.request(server)\n                  .get('/' + savedProject._id + '/requests/' + request_id + '/messages')\n                  .auth(email, pwd)\n                  .set('content-type', 'application/json')\n                  .end(function (err, res) {\n                    if (err) { console.error(\"err\", err); }\n                    if (log) { console.log(\"res.body\", res.body); }\n                    res.should.have.status(200);\n                    res.body.should.be.a('array');\n\n                    expect(res.body[0].sender).to.equal(savedUser._id.toString());\n                    expect(res.body[0].senderFullname).to.equal(\"senderFullname\");\n                    expect(res.body[0].recipient).to.equal(request_id);\n                    expect(res.body[0].text).to.equal(\"hello\");\n                    expect(res.body[0].id_project).to.equal(savedProject._id.toString());\n                    expect(res.body[0].createdBy).to.equal(savedUser._id.toString());\n                    expect(res.body[0].status).to.equal(200);\n                    expect(res.body[0].channel_type).to.equal(\"group\");\n                    expect(res.body[0].channel.name).to.equal(\"chat21\");\n                    // expect(res.body.request.request_id).to.equal(\"req123\");\n                    // expect(res.body.request.requester_id).to.equal(\"sender\");\n                    // expect(res.body.request.first_text).to.equal(\"text\");\n                    // expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                    // expect(res.body.request.createdBy).to.equal(savedUser._id.toString());\n                    // expect(res.body.request.messages_count).to.equal(1);\n                    // expect(res.body.request.status).to.equal(200);                                \n                    // expect(res.body.request.agents.length).to.equal(1);\n                    // expect(res.body.request.participants.length).to.equal(1);\n                    // expect(res.body.request.department).to.not.equal(null);\n                    // expect(res.body.request.lead).to.equal(null);               \n                    done();\n                  });\n              });\n          });\n        });\n      });\n    });\n\n  });\n\n\n  describe('/SendMessageSigninWithCustomToken', () => {\n\n    // mocha test/messageRoute.js  --grep 'sendMessageSigninWithCustomTokenOk'\n    it('sendMessageSigninWithCustomTokenOk', (done) => {\n\n      var email = \"test-sendmessagesigninwithcustomtokenok-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        // create(name, createdBy, settings)\n        projectService.create(\"test-sendMessageSigninWithCustomTokenOk\", savedUser._id).then(function (savedProject) {\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/keys/generate')\n            .auth(email, pwd)\n            .send()\n            .end((err, res) => {\n              if (err) { console.error(\"err\", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              expect(res.body.jwtSecret).to.not.equal(null);\n\n              // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n              var externalUserId = \"123\";\n              var externalUserObj = { _id: externalUserId, firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\" };\n              if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n              var signOptions = {\n                subject: 'userexternal',\n                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n              };\n\n              var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n              if (log) { console.log(\"jwtToken\", jwtToken); }\n\n              chai.request(server)\n                .post('/auth/signinWithCustomToken')\n                .set('Authorization', 'JWT ' + jwtToken)\n                //.send({ id_project: savedProject._id})\n                .send()\n                .end((err, res) => {\n                  if (err) { console.error(\"err\", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.success).to.equal(true);\n                  expect(res.body.user.email).to.equal(\"email2@email.com\");\n                  expect(res.body.user.firstname).to.equal(\"andrea\");\n\n                  expect(res.body.token).to.not.equal(undefined);\n                  expect(res.body.token).to.equal('JWT ' + jwtToken);\n\n                  let request_id = \"req_customtoken-\" + Date.now();\n                  chai.request(server)\n                    .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n                    .set('Authorization', 'JWT ' + jwtToken)\n                    .set('content-type', 'application/json')\n                    .send({ \"text\": \"text\" })\n                    .end(function (err, res) {\n                      if (err) { console.error(\"err\", err); }\n                      if (log) { console.log(\"res.body\", res.body); }\n                      res.should.have.status(200);\n                      res.body.should.be.a('object');\n\n                      expect(res.body.sender).to.equal(externalUserId);\n                      // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                      // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                      expect(res.body.recipient).to.equal(request_id);\n                      expect(res.body.text).to.equal(\"text\");\n                      expect(res.body.id_project).to.equal(savedProject._id.toString());\n                      expect(res.body.createdBy).to.equal(externalUserId);\n                      expect(res.body.status).to.equal(0);\n                      expect(res.body.request.request_id).to.equal(request_id);\n                      expect(res.body.request.first_text).to.equal(\"text\");\n                      expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                      expect(res.body.request.createdBy).to.equal(externalUserId);\n                      // expect(res.body.request.messages_count).to.equal(1);\n                      expect(res.body.request.status).to.equal(200);\n                      //expect(res.body.request.snapshot.agents.length).to.equal(1);\n                      expect(res.body.request.participants.length).to.equal(1);\n                      expect(res.body.request.department).to.not.equal(null);\n                      expect(res.body.request.lead).to.not.equal(null);\n\n                      chai.request(server)\n                        .get('/' + savedProject._id + '/requests/' + request_id)\n                        .auth(email, pwd)\n                        .set('content-type', 'application/json')\n                        .end(function (err, res) {\n                          \n                          if (err) { console.error(\"err\", err); }\n                          if (log) { console.log(\"res.body\", res.body); }\n                          \n                          expect(res.body.lead.lead_id).to.equal(externalUserId);\n                          expect(res.body.lead.email).to.equal(\"email2@email.com\");\n                          expect(res.body.lead.fullname).to.equal(\"andrea leo\");\n                          expect(res.body.requester.role).to.equal(\"user\");\n                          expect(res.body.requester.uuid_user).to.equal(externalUserId);\n                          expect(res.body.requester.id_user).to.equal(undefined);\n                          done()\n                        });\n                    });\n                });\n            });\n        });\n      });\n    });\n\n\n    // mocha test/messageRoute.js  --grep 'sendMessageSigninWithCustomTokenModified'\n    it('sendMessageSigninWithCustomTokenModified', (done) => {\n\n      var email = \"test-sendmessagesigninwithcustomtokenModified-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        // create(name, createdBy, settings)\n        projectService.create(\"test-sendMessageSigninWithCustomTokenModified\", savedUser._id).then(function (savedProject) {\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/keys/generate')\n            .auth(email, pwd)\n            .send()\n            .end((err, res) => {\n              if (err) { console.error(\"err\", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              expect(res.body.jwtSecret).to.not.equal(null);\n\n              // 'E11000 duplicate key error collection: tiledesk-test.users index: email_1 dup key: { email: \"email@email.com\" }' }\n              var externalUserId = \"123\";\n              var externalUserObj = { _id: externalUserId, firstname: \"andrea\", lastname: \"leo\", email: \"email2@email.com\" };\n\n              if (log) { console.log(\"externalUserObj\", externalUserObj); }\n\n              var signOptions = {\n                subject: 'userexternal',\n                audience: 'https://tiledesk.com/projects/' + savedProject._id,\n              };\n\n              var secret = res.body.jwtSecret;\n              var jwtToken = jwt.sign(externalUserObj, res.body.jwtSecret, signOptions);\n\n              if (log) { console.log(\"jwtToken\", jwtToken); }\n\n              chai.request(server)\n                .post('/auth/signinWithCustomToken')\n                .set('Authorization', 'JWT ' + jwtToken)\n                //.send({ id_project: savedProject._id})\n                .send()\n                .end((err, res) => {\n                  \n                  if (err) { console.error(\"err\", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n                  \n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.success).to.equal(true);\n                  expect(res.body.user.email).to.equal(\"email2@email.com\");\n                  expect(res.body.user.firstname).to.equal(\"andrea\");\n                  expect(res.body.token).to.not.equal(undefined);\n                  expect(res.body.token).to.equal('JWT ' + jwtToken);\n\n                  let request_id = \"req_customtokenmodified-\" + Date.now();\n                  chai.request(server)\n                    .post('/' + savedProject._id + '/requests/' + request_id + '/messages')\n                    .set('Authorization', 'JWT ' + jwtToken)\n                    .set('content-type', 'application/json')\n                    .send({ \"text\": \"text\" })\n                    .end(function (err, res) {\n                      if (err) { console.error(\"err\", err); }\n                      if (log) { console.log(\"res.body\", res.body); }\n                      res.should.have.status(200);\n                      res.body.should.be.a('object');\n\n                      expect(res.body.sender).to.equal(externalUserId);\n                      // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                      // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                      expect(res.body.recipient).to.equal(request_id);\n                      expect(res.body.text).to.equal(\"text\");\n                      expect(res.body.id_project).to.equal(savedProject._id.toString());\n                      expect(res.body.createdBy).to.equal(externalUserId);\n                      expect(res.body.status).to.equal(0);\n                      expect(res.body.request.request_id).to.equal(request_id);\n                      expect(res.body.request.first_text).to.equal(\"text\");\n                      expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                      expect(res.body.request.createdBy).to.equal(externalUserId);\n                      // expect(res.body.request.messages_count).to.equal(1);\n                      expect(res.body.request.status).to.equal(200);\n                      //expect(res.body.request.snapshot.agents.length).to.equal(1);\n                      expect(res.body.request.participants.length).to.equal(1);\n                      expect(res.body.request.department).to.not.equal(null);\n                      expect(res.body.request.lead).to.not.equal(null);\n                      expect(res.body.request.lead.email).to.equal(\"email2@email.com\");\n\n                      chai.request(server)\n                        .get('/' + savedProject._id + '/requests/' + request_id)\n                        .auth(email, pwd)\n                        .set('content-type', 'application/json')\n                        .end(function (err, res) {\n                          if (err) { console.error(\"err\", err); }\n                          if (log) { console.log(\"res.body\", res.body); }\n                          expect(res.body.lead.lead_id).to.equal(externalUserId);\n                          expect(res.body.lead.email).to.equal(\"email2@email.com\");\n                          expect(res.body.lead.fullname).to.equal(\"andrea leo\");\n                          expect(res.body.requester.role).to.equal(\"user\");\n                          expect(res.body.requester.uuid_user).to.equal(externalUserId);\n                          expect(res.body.requester.id_user).to.equal(undefined);\n\n                          externalUserObj.email = \"email33@email.com\";\n\n                          jwtToken = jwt.sign(externalUserObj, secret, signOptions);\n                          if (log) { console.log(\"jwtToken2\", jwtToken); }\n\n                          chai.request(server)\n                            .post('/auth/signinWithCustomToken')\n                            .set('Authorization', 'JWT ' + jwtToken)\n                            //.send({ id_project: savedProject._id})\n                            .send()\n                            .end((err, res) => {\n                              if (err) { console.error(\"err\", err); }\n                              if (log) { console.log(\"res.body\", res.body); }\n                              res.should.have.status(200);\n                              res.body.should.be.a('object');\n                              expect(res.body.success).to.equal(true);\n                              expect(res.body.user.email).to.equal(\"email33@email.com\");\n                              expect(res.body.user.firstname).to.equal(\"andrea\");\n\n                              let request2_id = \"req_customtokenmodified2-\" + Date.now();\n                              \n                              chai.request(server)\n                                .post('/' + savedProject._id + '/requests/' + request2_id + '/messages')\n                                .set('Authorization', 'JWT ' + jwtToken)\n                                .set('content-type', 'application/json')\n                                .send({ \"text\": \"text\" })\n                                .end(function (err, res) {\n                                  \n                                  if (err) { console.error(\"err\", err); }\n                                  if (log) { console.log(\"res.body\", res.body); }\n                                  \n                                  res.should.have.status(200);\n                                  res.body.should.be.a('object');\n                                  expect(res.body.request.lead.email).to.equal(\"email33@email.com\");\n                                  done()\n\n                                });\n                            });\n                        });\n                    });\n                });\n            });\n        });\n      });\n    });\n\n  });\n\n\n  // mocha test/messageRoute.js  --grep 'sendMessageSigninAnonym'\n  describe('/SendMessageSigninAnonym', () => {\n\n    it('sendMessageSigninAnonym', (done) => {\n\n      var email = \"test-sendmessagesigninanonym-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        // create(name, createdBy, settings)\n        projectService.create(\"test-sendMessageSigninAnonym\", savedUser._id).then(function (savedProject) {\n\n          chai.request(server)\n            .post('/auth/signinAnonymously')\n            .send({ id_project: savedProject._id })\n            .end((err, res) => {\n              \n              if (err) { console.error(\"err\", err); }\n              if (log) { console.log(\"res.body\", res.body); }\n              \n              res.should.have.status(200);\n              res.body.should.be.a('object');\n              \n              var userId = res.body.user._id;\n              expect(res.body.success).to.equal(true);\n              expect(res.body.user.email).to.equal(undefined);\n              expect(res.body.user.firstname).to.contains(\"guest#\");      // guest_here                                          \n              expect(res.body.token).to.not.equal(undefined);\n\n              var rid = 'support-group-' + Date.now();\n\n              chai.request(server)\n                .post('/' + savedProject._id + '/requests/' + rid + '/messages')\n                .set('Authorization', res.body.token)\n                .set('content-type', 'application/json')\n                .send({ \"text\": \"text\" })\n                .end(function (err, res) {\n                  if (err) { console.error(\"err\", err); }\n                  if (log) { console.log(\"res.body\", res.body); }\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n\n                  expect(res.body.sender).to.equal(userId);\n                  // expect(res.body.sender).to.equal(savedProjectAndPU.project_user._id.toString());\n                  // expect(res.body.senderFullname).to.equal(\"senderFullname\");\n                  expect(res.body.recipient).to.equal(rid);\n                  expect(res.body.text).to.equal(\"text\");\n                  expect(res.body.id_project).to.equal(savedProject._id.toString());\n                  expect(res.body.createdBy).to.equal(userId);\n                  expect(res.body.status).to.equal(0);\n                  expect(res.body.request.request_id).to.equal(rid);\n                  expect(res.body.request.first_text).to.equal(\"text\");\n                  expect(res.body.request.id_project).to.equal(savedProject._id.toString());\n                  expect(res.body.request.createdBy).to.equal(userId);\n                  // expect(res.body.request.messages_count).to.equal(1);\n                  expect(res.body.request.status).to.equal(200);\n                  //expect(res.body.request.snapshot.agents.length).to.equal(1);\n                  expect(res.body.request.participants.length).to.equal(1);\n                  expect(res.body.request.department).to.not.equal(null);\n                  expect(res.body.request.lead).to.not.equal(null);\n\n                  chai.request(server)\n                    .get('/' + savedProject._id + '/requests/' + rid)\n                    .auth(email, pwd)\n                    .set('content-type', 'application/json')\n                    .end(function (err, res) {\n                      if (err) { console.error(\"err\", err); }\n                      if (log) { console.log(\"res.body\", res.body); }\n                      expect(res.body.lead.lead_id).to.equal(userId);\n                      expect(res.body.lead.email).to.equal(undefined);\n                      expect(res.body.lead.fullname).to.contains(\"guest#\"); // guest_here\n                      expect(res.body.requester.role).to.equal(\"guest\");\n                      expect(res.body.requester.uuid_user).to.equal(userId);\n                      expect(res.body.requester.id_user).to.equal(undefined);\n                      done()\n                    });\n                });\n            });\n        });\n      });\n    });\n\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/messageService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'critical';\n\nvar expect = require('chai').expect;\n\nvar chai = require(\"chai\");\nchai.config.includeStack = true;\n\nlet log = false;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\nlet should = chai.should();\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\nrequire('../services/mongoose-cache-fn')(mongoose);\n\nvar requestService = require('../services/requestService');\nvar messageService = require('../services/messageService');\nvar projectService = require('../services/projectService');\n\nvar Request = require(\"../models/request\");\n\nvar { QuoteManager } = require('../services/QuoteManager');\n\n// CONNECT REDIS - CHECK IT\nconst { TdCache } = require('../utils/TdCache');\nlet tdCache = new TdCache({\n  host: '127.0.0.1',\n  port: '6379'\n});\n\ntdCache.connect();\n\ndescribe('messageService', function () {\n\n  var userid = \"5badfe5d553d1844ad654072\";\n\n  // it('createMessageQuote', function (done) {\n  //   // this.timeout(10000);\n  //   let qm = new QuoteManager({ tdCache: tdCache });\n  //   qm.start();\n\n  //   projectService.create(\"test1\", userid).then(function (savedProject) {\n  //     // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n\n  //     messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"hello\",\n  //       savedProject._id, userid, undefined, { a1: \"a1\" }, undefined, undefined, \"it\").then(function (savedMessage) {\n  //         winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n  //         expect(savedMessage.text).to.equal(\"hello\");\n  //         expect(savedMessage.sender).to.equal(userid);\n  //         expect(savedMessage.senderFullname).to.equal(\"test sender\");\n  //         expect(savedMessage.recipient).to.equal(\"testrecipient-createMessage\");\n  //         expect(savedMessage.language).to.equal(\"it\");\n  //         expect(savedMessage.attributes.a1).to.equal(\"a1\");\n  //         expect(savedMessage.channel_type).to.equal(\"group\");\n  //         expect(savedMessage.channel.name).to.equal(\"chat21\");\n\n\n  //         setTimeout(async () => {\n  //           let obj = { createdAt: new Date() }\n\n  //           let quotes = await qm.getAllQuotes(savedProject, obj);\n  //           quotes.messages.quote.should.be.a('number');\n  //           expect(quotes.messages.quote).to.equal(1);\n            \n  //           done();\n            \n  //         }, 1000);\n\n\n  //       }).catch(function (err) {\n  //         assert.isNotOk(err, 'Promise error');\n  //         done();\n  //       });\n\n  //   });\n  // }).timeout(10000);\n\n  it('createMessage', function (done) {\n    // this.timeout(10000);\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n\n      let quoteManager = new QuoteManager({ project: savedProject, tdCache: tdCache })\n      quoteManager.start();\n\n      messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"hello\",\n        savedProject._id, userid, undefined, { a1: \"a1\" }, undefined, undefined, \"it\").then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"hello\");\n          expect(savedMessage.sender).to.equal(userid);\n          expect(savedMessage.senderFullname).to.equal(\"test sender\");\n          expect(savedMessage.recipient).to.equal(\"testrecipient-createMessage\");\n          expect(savedMessage.language).to.equal(\"it\");\n          expect(savedMessage.attributes.a1).to.equal(\"a1\");\n          expect(savedMessage.channel_type).to.equal(\"group\");\n          expect(savedMessage.channel.name).to.equal(\"chat21\");\n          done();\n\n        }).catch(function (err) {\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n    });\n  });\n\n  it('createMessageAndFillTextWithTranslation', function (done) {\n    // this.timeout(10000);\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n\n      let quoteManager = new QuoteManager({ project: savedProject, tdCache: tdCache })\n      quoteManager.start();\n\n      messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"\",\n        savedProject._id, userid, undefined, { a1: \"a1\" }, \"file\", { name: \"filename\", type: \"audio/mpeg\", src: \"filenameurl.com\"}, \"it\").then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"This is a mock trancripted audio\");\n          done();\n\n        }).catch(function (err) {\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n    });\n  });\n\n\n\n\n  it('createMessageAndUpdateTwoMessagesCount', function (done) {\n    // this.timeout(10000);  \n    // projectService.create(\"test1\", userid).then(function(savedProject) {\n    projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n      var savedProject = savedProjectAndPU.project;\n      // attento reqid\n      // requestService.createWithId(\"request_id-createTwoMessage\", \"requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n      requestService.createWithIdAndRequester(\"request_id-createTwoMessage-\" + new Date(), savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n        messageService.create(userid, \"test sender\", savedRequest.request_id, \"hello\", savedProject._id, userid).then(function (savedMessage) {\n            // Promise.all([requestService.incrementMessagesCountByRequestId(savedRequest.request_id, savedProject._id),\n            //   requestService.incrementMessagesCountByRequestId(savedRequest.request_id, savedProject._id)]).then(function(savedMessage) {\n\n            Request.findOne({ \"request_id\": \"request_id-createTwoMessage\", \"id_project\": savedProject._id }).exec().then(function (req) {\n              if (log) { console.log(\"test resolve\", req); };\n              // expect(req.messages_count).to.equal(2);\n\n              done();\n            }).catch(function (err) {\n              winston.error(\"test reject\", err);\n              assert.isNotOk(err, 'Promise error');\n              done();\n            });\n            // });\n          });\n      });\n    });\n\n\n  }).timeout(4000);\n\n\n\n\n\n\n\n\n  // mocha test/messageService.js  --grep 'createMessageMultiLanguageSimple'\n  it('createMessageMultiLanguageSimple', function (done) {\n    // this.timeout(10000);\n\n\n    var messageTransformerInterceptor = require('../pubmodules/messageTransformer/messageTransformerInterceptor');\n    if (log) { console.log(\"messageTransformerInterceptor\", messageTransformerInterceptor); }\n    messageTransformerInterceptor.listen();\n\n\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata) {\n      messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"${LABEL_PLACEHOLDER}\",\n        savedProject._id, userid).then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"type your message..\");\n          expect(savedMessage.sender).to.equal(userid);\n          expect(savedMessage.senderFullname).to.equal(\"test sender\");\n          expect(savedMessage.recipient).to.equal(\"testrecipient-createMessage\");\n          done();\n\n        }).catch(function (err) {\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n    });\n  });\n\n\n\n  it('createMessageMultiLanguageNOTFound', function (done) {\n    // this.timeout(10000);\n\n\n    var messageTransformerInterceptor = require('../pubmodules/messageTransformer/messageTransformerInterceptor');\n    if (log) { console.log(\"messageTransformerInterceptor\", messageTransformerInterceptor); }\n    messageTransformerInterceptor.listen();\n\n\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy) {\n      messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"${NOTFOUND_LABEL}\",\n        savedProject._id, userid).then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"${NOTFOUND_LABEL}\");\n          expect(savedMessage.sender).to.equal(userid);\n          expect(savedMessage.senderFullname).to.equal(\"test sender\");\n          expect(savedMessage.recipient).to.equal(\"testrecipient-createMessage\");\n          done();\n\n        }).catch(function (err) {\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n    });\n  });\n\n\n\n\n\n  it('createMessageMultiLanguageNOLanguage', function (done) {\n    // this.timeout(10000);\n\n\n    var messageTransformerInterceptor = require('../pubmodules/messageTransformer/messageTransformerInterceptor');\n    if (log) { console.log(\"messageTransformerInterceptor\", messageTransformerInterceptor); }\n    messageTransformerInterceptor.listen();\n\n\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language) {\n      messageService.create(userid, \"test sender\", \"testrecipient-createMessage\", \"${LABEL_PLACEHOLDER}\",\n        savedProject._id, userid, undefined, undefined, undefined, undefined, \"XXXX\").then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"type your message..\"); //EN default\n          expect(savedMessage.sender).to.equal(userid);\n          expect(savedMessage.senderFullname).to.equal(\"test sender\");\n          expect(savedMessage.recipient).to.equal(\"testrecipient-createMessage\");\n          done();\n\n        }).catch(function (err) {\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n\n    });\n  });\n\n\n\n\n\n\n\n  // mocha test/messageService.js  --grep 'createMessageMicroLanguageWithAttribute'\n\n  it('createMessageMicroLanguageWithAttribute', function (done) {\n    // this.timeout(10000);\n\n\n    var microLanguageTransformerInterceptor = require('../pubmodules/messageTransformer/microLanguageTransformerInterceptor');\n    if (log) { console.log(\"microLanguageTransformerInterceptor\", microLanguageTransformerInterceptor); }\n    microLanguageTransformerInterceptor.listen();\n\n\n\n    projectService.create(\"test1\", userid).then(function (savedProject) {\n      // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata) {\n      messageService.create(\"bot_\" + userid, \"test sender\", \"testrecipient-createMessageMicroLanguageWithAttribute\", \"ciao\\n* Button1\",\n        savedProject._id, userid, undefined, { microlanguage: true }).then(function (savedMessage) {\n          winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n          expect(savedMessage.text).to.equal(\"ciao\");\n          expect(savedMessage.type).to.equal(\"text\");\n          expect(savedMessage.attributes._raw_message).to.equal(\"ciao\\n* Button1\", \"attachment\");\n          expect(savedMessage.attributes.attachment.type).to.equal(\"template\");\n          expect(savedMessage.attributes.attachment.buttons[0].value).to.equal(\"Button1\");\n          expect(savedMessage.sender).to.equal(\"bot_\" + userid);\n          expect(savedMessage.senderFullname).to.equal(\"test sender\");\n          expect(savedMessage.recipient).to.equal(\"testrecipient-createMessageMicroLanguageWithAttribute\");\n          done();\n\n        })\n    });\n  });\n\n\n\n});\n"
  },
  {
    "path": "test/mock/chatbotMock.js",
    "content": "const existing_chatbot_mock = {\n    _id: \"63234fd6c69f188814749e1b\",\n    webhook_enabled: false,\n    type: \"tilebot\",\n    secret: \"a3577185-a8bf-4547-a000-6ba81ccb0460\",\n    language: \"en\",\n    description: \"A Tiledesk bot\",\n    id_project: \"63234fd6c69f188814749e15\",\n    trashed: false,\n    createdBy: \"system\",\n    createdAt: \"2022-09-15T15:32:06.491Z\",\n    updatedAt: \"2022-09-15T15:32:06.491Z\",\n    name: \"Bot\",\n    attributes: {\n        globals: {\n            \"mykey1\": \"myvalue1\",\n            \"mykey2\": \"myvalue2\"\n        }\n    },\n    intents: [\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"👨🏻‍🦰 I want an agent\",\n            \"answer\": \"We are looking for an operator.. \\\\agent\",\n            \"intent_display_name\": \"agent_handoff\",\n            \"language\": \"en\",\n            \"form\": {\n                \"cancelCommands\": [\n                    \"Cancel\"\n                ],\n                \"cancelReply\": \"Form canceled\",\n                \"id\": 1,\n                \"name\": \"Base\",\n                \"fields\": [\n                    {\n                        \"name\": \"userFullname\",\n                        \"type\": \"text\",\n                        \"label\": \"What is your name?\",\n                        \"regex\": \"/^.{1,}$/\"\n                    },\n                    {\n                        \"name\": \"userEmail\",\n                        \"type\": \"text\",\n                        \"regex\": \"/^.{1,}$/\",\n                        \"label\": \"Hi ${userFullname}. Just one last question. What's your business email? 🙂\",\n                        \"errorLabel\": \"${userFullname} this email address is invalid\\n\\nCan you insert a correct email address?\"\n                    }\n                ]\n            }\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"Close\\nResolved\",\n            \"answer\": \"\\\\close\",\n            \"intent_display_name\": \"2SN1EO\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"defaultFallback\",\n            \"answer\": \"Could you please rephrase the question? Use short sentences and send it in just one message.\\n\\nAs an alternative you can go back to the Main Menu👇🏼\\n* ↩︎ Main Menu tdAction:start\",\n            \"intent_display_name\": \"defaultFallback\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"\\\\start\",\n            \"answer\": \"Hello 👋. My name is Dean. Lovely of you to get in touch!\\nMy only purpose in life is to help you navigate the frustrating waters of having to decide what to have for dinner.\\n\\nYet again. \\n\\nAs a proud gourmet and wine connoisseur, I'll take the freedom to give you some interesting suggestions.\\n\\nChoose one of the below for a juicy description.\\n* 🍕The most ordered type of pizza\\n* 👌Chef's favourite\\n* 🍣The most highly rated sushi \\n* 🍹Cocktail of the month\\n* 🍻Oktoberfest specials\\n* 🕹️ Or play a game while waiting\",\n            \"intent_display_name\": \"start\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"What can you do?\\nWho are you?\",\n            \"answer\": \"Hi, I'm Dean 🤖\\n\\nYou can find more about me [here](https://tiledesk.com/chatbot-for-customer-service)\\ntdImage:https://console.tiledesk.com/assets/images/tily-welcomebot.gif\\n* ↩︎ Start over tdAction:start\",\n            \"intent_display_name\": \"7jLAZf\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"Sample Action\",\n            \"answer\": \"Hello 👋 Would you like to take a closer look at our offer?\\n* Yes, please tdAction:yes_action\\n* No tdAction:no_action\",\n            \"intent_display_name\": \"action1\",\n            \"language\": \"en\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"Back to start\",\n            \"answer\": \"Choose one of the below for a juicy description.\\n* 🍕The most ordered type of pizza\\n* 👌Chef's favourite\\n* 🍣The most highly rated sushi\\n* 🍹Cocktail of the month\\n* 🍻Oktoberfest specials\",\n            \"language\": \"en\",\n            \"intent_display_name\": \"back\"\n        },\n        {\n            \"webhook_enabled\": false,\n            \"enabled\": true,\n            \"question\": \"🍹Cocktail of the month\",\n            \"answer\": \"Looks like you may need a designated driver with this one...\\ntdImage:https://hips.hearstapps.com/hmg-prod.s3.amazonaws.com/images/cosmopolitan-1592951320.jpg\\n* Back to start\",\n            \"language\": \"en\",\n            \"intent_display_name\": \"booze\"\n        }\n    ]\n}\n\nconst empty_chatbot_mock = {\n    webhook_enabled: false,\n    type: 'tilebot',\n    secret: '0896478d-8eb3-4240-a6eb-cf2ea27b1a8e',\n    language: 'en',\n    public: false,\n    _id: '638db4d551144cfc72231bfd',\n    name: 'Dean for Quick Replies',\n    description: \"This chatbot can recognize customers' purpose, context, and content. Then Providing some buttons as quick replies\",\n    id_project: '6321b12a30e19d61d65ff080',\n    trashed: false,\n    createdBy: '6321b12930e19d61d65ff045',\n    createdAt: '2022-12-05T09:07:33.722Z',\n    updatedAt: '2022-12-05T09:07:33.722Z',\n    __v: 0\n}\n\nconst import_faqs_res_mock = {\n    success: true,\n    msg: \"Intents imported successfully\"\n}\n\nmodule.exports = { existing_chatbot_mock, empty_chatbot_mock, import_faqs_res_mock };"
  },
  {
    "path": "test/mock/emailMock.js",
    "content": "const emailMock = {\n    from: 'email@from.com',\n    to: 'to@from.com',\n    subject: 'Email Subject',\n    body: 'Email Body',\n    createdAt: new Date('2023-10-25T08:45:54.058Z') \n}\n\nmodule.exports = { emailMock }"
  },
  {
    "path": "test/mock/messageMock.js",
    "content": "const messageMock = {\n    senderFullname: 'Test Sender',\n    type: 'text',\n    text: 'Hello!',\n    conversationWith: \"support-group-63d540d370133e00128d6e59-f384a142258c4f2da2341770213cb5e7\",\n    recipientFullname: \"Test Sender \",\n    projectid: \"63d540d370133e00128d6e59\",\n    channelType: \"group\",\n    createdBy: '5badfe5d553d1844ad654072',\n    attributes: {}\n}\n\n// const messageMock = {\n//     \"senderFullname\": \"guest#32d1 \",\n//     \"text\": \"test 1\",\n//     \"type\": \"text\",\n//     \"metadata\": \"\",\n//     \"conversationWith\": \"support-group-63d540d370133e00128d6e59-f384a142258c4f2da2341770213cb5e7\",\n//     \"recipientFullname\": \"guest#32d1 \",\n//     \"attributes\": {\n//         \"departmentId\": \"649c4644d71dcc0013909e6c\",\n//         \"departmentName\": \"NEW MESSAGE TEST\",\n//         \"ipAddress\": \"5.91.158.145\",\n//         \"client\": \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36\",\n//         \"sourcePage\": \"http://localhost:4203/assets/twp/index-dev.html?tiledesk_projectid=63d540d370133e00128d6e59&tiledesk_align=right&project_name=Demo%20email&tiledesk_singleConversation=false&tiledeks_nativeRating=true&tiledesk_mobileMarginY=35px&tiledesk_showLogoutOption=true&tiledesk_isLogEnabled=true&quiz\",\n//         \"sourceTitle\": \"Widget test page\",\n//         \"projectId\": \"63d540d370133e00128d6e59\",\n//         \"widgetVer\": \"v.5.0.71-rc.4\",\n//         \"payload\": [],\n//         \"userFullname\": \"guest#32d1 \",\n//         \"requester_id\": \"32d1a53f-ec47-459a-9956-b52392e99079\",\n//         \"userEmail\": \"ddd@dd.it\",\n//         \"preChatForm\": null,\n//         \"cookie\": \"_ga=GA1.1.1543507943.1667828274; _cioid=5fd34e4249610e0034985078; hubspotutk=2edc9ab132fa5d509da1f3b63bbd4363; __stripe_mid=83a31c6b-0f17-4db4-8141-db2b31aa8e00e3feb8; ajs_user_id=638a2d68903f950014304c2a; ajs_anonymous_id=62a9e436-bd3f-4ac3-a1e7-78aad9d3e428; __hssrc=1; _clck=1xdqm39|2|fdt|0|1253; __hstc=181257784.2edc9ab132fa5d509da1f3b63bbd4363.1679477625553.1690960089317.1690977927174.140; _ga_BKHKLWGG6F=GS1.1.1690977924.245.1.1690984596.0.0.0; _clsk=1qgjsgo|1690984597675|2|1|y.clarity.ms/collect\",\n//         \"action\": \"#d8f15799-4209-46b9-874d-ca5818873146\",\n//         \"subtype\": \"\",\n//         \"lang\": \"en\",\n//         \"tempUID\": \"385c56fa-471a-4ce9-a7d8-7b9d179a85b0\"\n//     },\n//     \"projectid\": \"63d540d370133e00128d6e59\",\n//     \"channelType\": \"group\"\n// }\n\n\n\nmodule.exports = { messageMock }"
  },
  {
    "path": "test/mock/projectMock.js",
    "content": "const mockProjectUser = {\n  \"_id\": \"64e36f5dbf72263f7c056666\",\n  \"id_project\": \"64e36f5dbf72263f7c059999\",\n  \"id_user\": \"64e36f5dbf72263f7c057777\",\n  \"role\": 'owner',\n  \"user_available\": true,\n  \"createdBy\": \"64e36f5dbf72263f7c057777\",\n  \"updatedBy\": \"64e36f5dbf72263f7c057777\"\n}\n\nconst mockProjectFreeTrialPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"profile\": {\n    \"name\": \"free\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"free\"\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-16T08:45:54.058Z')\n}\n\nconst mockProjectSandboxPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"profile\": {\n    \"name\": \"Sandbox\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"free\",\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-20T08:45:54.058Z')\n}\n\nconst mockProjectBasicPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"isActiveSubscription\": true,\n  \"profile\": {\n    \"name\": \"Basic\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"payment\",\n    \"subStart\": new Date('2023-10-20T08:45:54.058Z')\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-16T08:45:54.058Z')\n}\n\nconst mockProjectPremiumPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"profile\": {\n    \"name\": \"Premium\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"payment\",\n    \"subStart\": new Date('2023-10-20T08:45:54.058Z')\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-16T08:45:54.058Z')\n}\n\nconst mockProjectPremiumPlan2 = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"isActiveSubscription\": true,\n  \"profile\": {\n    \"name\": \"Premium\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"payment\",\n    \"subStart\": new Date('2024-01-31T10:00:00.058Z')\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2024-01-20T10:00:00.058Z')\n}\n\nconst mockProjectCustomPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"ipFilterDenyEnabled\": false,\n  \"ipFilterDeny\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"profile\": {\n    \"name\": \"Custom\",\n    \"trialDays\": 14,\n    \"agents\": 0,\n    \"type\": \"payment\",\n    \"subStart\": new Date('2023-10-20T08:45:54.058Z')\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-16T08:45:54.058Z')\n}\n\nconst mockOldProjecPlusPlan = {\n  \"_id\": \"64e36f5dbf72263f7c059999\",\n  \"status\": 100,\n  \"ipFilterEnabled\": false,\n  \"ipFilter\": [],\n  \"name\": \"mock-project\",\n  \"activeOperatingHours\": false,\n  \"createdBy\": \"64e36f5cbf72263f7c05ba36\",\n  \"profile\": {\n    \"name\": \"Plus\",\n    \"trialDays\": 30,\n    \"agents\": 40,\n    \"type\": \"payment\",\n    \"subStart\": new Date('2023-10-20T08:45:54.058Z'),\n    \"subEnd\": new Date('2024-3-29T08:45:54.058Z'),\n    \"subscriptionId\": \"sub_1MjKLHG7zhjGozkvDsXITiDf\",\n    \"last_stripe_event\": \"invoice.payment_succeeded\"\n  },\n  \"versions\": 20115,\n  \"channels\": [\n    {\n      \"name\": \"chat21\"\n    }\n  ],\n  \"createdAt\": new Date('2023-10-18T08:45:54.058Z'),\n  \"updatedAt\": new Date('2023-10-20T08:45:54.058Z'),\n  \"bannedUsers\": [],\n  \"ipFilterDeny\": [],\n  \"ipFilterDenyEnabled\": false\n}\n\nmodule.exports = { mockProjectUser, mockProjectFreeTrialPlan, mockProjectSandboxPlan, mockProjectBasicPlan, mockProjectPremiumPlan, mockProjectPremiumPlan2, mockProjectCustomPlan, mockOldProjecPlusPlan };"
  },
  {
    "path": "test/mock/requestMock.js",
    "content": "const requestMock = {\n    \"_id\": \"652e46f283a36264e8e8b331\",\n    \"status\": 200,\n    \"preflight\": false,\n    \"hasBot\": false,\n    \"participants\": [\n        \"652e46f183a36264e8e8b256\"\n    ],\n    \"priority\": \"medium\",\n    \"followers\": [],\n    \"participantsAgents\": [\n        \"652e46f183a36264e8e8b256\"\n    ],\n    \"participantsBots\": [],\n    \"request_id\": \"support-group-652e46f183a36264e8e8b27b-16c334bd0d214579a4c98f2f0e7794ad\",\n    \"requester\": \"652e46f183a36264e8e8b299\",\n    \"lead\": \"652e46f283a36264e8e8b323\",\n    \"first_text\": \"first_text\",\n    \"department\": \"652e46f183a36264e8e8b2a7\",\n    \"assigned_at\": new Date('2023-10-17T08:45:54.058Z'),\n    \"id_project\": \"652e46f183a36264e8e8b27b\",\n    \"createdBy\": \"652e46f183a36264e8e8b256\",\n    \"channel\": {\n        \"name\": \"chat21\"\n    },\n    \"createdAt\": new Date('2023-10-19T08:45:54.058Z')\n}\n\n// const requestMock = {\n//     \"_id\": \"652e46f283a36264e8e8b331\",\n//     \"status\": 200,\n//     \"preflight\": false,\n//     \"hasBot\": false,\n//     \"participants\": [\n//         \"652e46f183a36264e8e8b256\"\n//     ],\n//     \"priority\": \"medium\",\n//     \"followers\": [],\n//     \"participantsAgents\": [\n//         \"652e46f183a36264e8e8b256\"\n//     ],\n//     \"participantsBots\": [],\n//     \"request_id\": \"support-group-652e46f183a36264e8e8b27b-16c334bd0d214579a4c98f2f0e7794ad\",\n//     \"requester\": \"652e46f183a36264e8e8b299\",\n//     \"lead\": \"652e46f283a36264e8e8b323\",\n//     \"first_text\": \"first_text\",\n//     \"department\": \"652e46f183a36264e8e8b2a7\",\n//     \"assigned_at\": new Date('2023-10-17T08:45:54.058Z'),\n//     \"id_project\": \"652e46f183a36264e8e8b27b\",\n//     \"createdBy\": \"652e46f183a36264e8e8b256\",\n//     \"channel\": {\n//         \"name\": \"chat21\"\n//     },\n//     \"snapshot\": {\n//         \"department\": {\n//             \"routing\": \"assigned\",\n//             \"default\": true,\n//             \"status\": 1,\n//             \"_id\": \"652e46f183a36264e8e8b2a7\",\n//             \"name\": \"Default Department\",\n//             \"id_project\": \"652e46f183a36264e8e8b27b\",\n//             \"createdBy\": \"652e46f183a36264e8e8b256\",\n//             \"tags\": [],\n//             \"createdAt\": new Date('2023-10-17T08:41:48.140Z'),\n//             \"updatedAt\": new Date('2023-10-17T08:41:48.140Z'),\n//             \"__v\": 0\n//         },\n//         \"agents\": [\n//             {\n//                 \"user_available\": true,\n//                 \"number_assigned_requests\": 0,\n//                 \"last_login_at\": new Date('2023-10-17T08:33:51.266Z'),\n//                 \"status\": \"active\",\n//                 \"_id\": \"652e46f183a36264e8e8b299\",\n//                 \"id_project\": \"652e46f183a36264e8e8b27b\",\n//                 \"id_user\": \"652e46f183a36264e8e8b256\",\n//                 \"role\": \"owner\",\n//                 \"createdBy\": \"652e46f183a36264e8e8b256\",\n//                 \"tags\": [],\n//                 \"createdAt\": new Date('2023-10-17T08:33:53.484Z'),\n//                 \"updatedAt\": new Date('2023-10-17T08:33:53.484Z'),\n//                 \"__v\": 0\n//             }\n//         ],\n//         \"availableAgentsCount\": 1,\n//         \"requester\": {\n//             \"user_available\": true,\n//             \"number_assigned_requests\": 0,\n//             \"last_login_at\": new Date('2023-10-17T08:33:51.266Z'),\n//             \"status\": \"active\",\n//             \"_id\": \"652e46f183a36264e8e8b299\",\n//             \"id_project\": \"652e46f183a36264e8e8b27b\",\n//             \"id_user\": \"652e46f183a36264e8e8b256\",\n//             \"role\": \"owner\",\n//             \"createdBy\": \"652e46f183a36264e8e8b256\",\n//             \"tags\": [],\n//             \"createdAt\": new Date('2023-10-17T08:33:53.484Z'),\n//             \"updatedAt\": new Date('2023-10-17T08:33:53.484Z'),\n//             \"__v\": 0\n//         },\n//         \"lead\": {\n//             \"tags\": [],\n//             \"status\": 100,\n//             \"_id\": \"652e46f283a36264e8e8b323\",\n//             \"lead_id\": \"652e46f183a36264e8e8b256\",\n//             \"fullname\": \"Test Firstname Test lastname\",\n//             \"email\": \"test-request-create-1697531633210@email.com\",\n//             \"id_project\": \"652e46f183a36264e8e8b27b\",\n//             \"createdBy\": \"system\",\n//             \"createdAt\": new Date('2023-10-17T08:33:54.035Z'),\n//             \"updatedAt\": new Date('2023-10-17T08:33:54.035Z'),\n//             \"__v\": 0\n//         }\n//     },\n//     \"tags\": [],\n//     \"notes\": [],\n//     \"channelOutbound\": {\n//         \"name\": \"chat21\"\n//     },\n//     \"smartAssignment\": true,\n//     \"createdAt\": new Date('2023-10-19T08:45:54.058Z'),\n//     \"updatedAt\": new Date('2023-10-19T08:45:54.058Z'),\n//     \"ticket_id\": 1,\n//     \"__v\": 0\n// }\n\nmodule.exports = { requestMock }"
  },
  {
    "path": "test/mock/tdCacheMock.js",
    "content": "\nclass MockTdCache {\n\n    constructor() {\n        this.db = new Map();\n    }\n\n    async set(k, v) {\n        return new Promise((resolve, reject) => {\n            this.db.set(k, \"\" + v);\n            resolve();\n        })\n    }\n\n    async incr(k) {\n        let value = await this.get(k);\n        if (value == undefined || value == null) {\n            value = 0;\n        }\n\n        try {\n            value = Number(value);\n        } catch(error) {\n            value = 0;\n        }\n\n        let v_incr = Number(value) + 1;\n        this.db.set(k, \"\" + v_incr);\n    }\n\n    async get(k) {\n        return new Promise((resolve, reject) => {\n            const v = this.db.get(k);\n            resolve(v);\n        })\n    }\n\n    async del(k) {\n        return new Promise((resolve, reject) => {\n            const v = this.db.delete(k);\n            resolve();\n        })\n    }\n}\n\nmodule.exports = { MockTdCache };"
  },
  {
    "path": "test/openaiRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n//process.env.AI_MODELS = 'gpt-3.5-turbo:0.6;gpt-4:25;gpt-4-turbo-preview:12;gpt-4o:6'\n//process.env.AI_MODELS = 'gpt-3.5-turbo;gpt-4-turbo-preview;gpt-4o'\nprocess.env.AI_MODELS = '    gpt-3.5-turbo:0   .6;gpt -4:2  5; g  pt-4-tur   bo-preview:12;gp   t-4o:6'\n\n\nlet log = false;\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('OpenaiRoute', () => {\n\n    describe('/create', () => {\n\n        // it('completions', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n        //         projectService.create(\"test-openai-create\", savedUser._id).then((savedProject) => {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n\n        //                     chai.request(server)\n        //                         .put('/' + savedProject._id + \"/kbsettings/\" + res.body._id)\n        //                         .auth(email, pwd)\n        //                         .send({ gptkey: \"sk-12345678\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"add kb to kb settings res.body: \", res.body); }\n        //                             res.should.have.status(200);\n        //                             res.body.should.be.a('object');\n\n        //                             chai.request(server)\n        //                                 .post('/' + savedProject._id + '/openai')\n        //                                 .auth(email, pwd)\n        //                                 .send({ question: \"Provide 3 names for a dog\", context: \"you are an awesome assistant\", max_tokens: 100, temperature: 0, model: \"gpt-3.5-turbo\" })\n        //                                 .end((err, res) => {\n        //                                     console.log(\"res.body (1): \", res.body);\n        //                                     console.log(\"res.status: \", res.status);\n\n        //                                     done();\n        //                                 })\n\n        //                         })\n        //                 })\n        //         })\n        //     })\n        // }).timeout(20000)\n\n        // it('completions missing gptkey', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n        //         projectService.create(\"test-openai-create\", savedUser._id).then((savedProject) => {\n\n        //             chai.request(server)\n        //                 .post('/' + savedProject._id + '/kbsettings')\n        //                 .auth(email, pwd)\n        //                 .send({}) // can be empty\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"create kbsettings res.body: \", res.body); }\n        //                     res.should.have.status(200);\n        //                     res.body.should.be.a('object');\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + '/openai')\n        //                         .auth(email, pwd)\n        //                         .send({ question: \"Provide 3 names for a dog\", context: \"you are an awesome assistant\", max_tokens: 100, temperature: 0, model: \"gpt-3.5-turbo\" })\n        //                         .end((err, res) => {\n        //                             if (log) { console.log(\"res.body: \", res.body); }\n        //                             console.log(\"res.body: \", res.body)\n        //                             // res.should.have.status(400);\n        //                             // res.body.should.be.a('object');\n        //                             // expect(res.body.success).to.equal(false);\n        //                             // expect(res.body.message).to.equal(\"Missing gptkey parameter\");\n\n        //                             done();\n        //                         })\n        //                 })\n        //         })\n        //     })\n        // }).timeout(20000)\n\n        it('newCompletionsMissingGptkey', (done) => {\n\n            let original_value = process.env.GPTKEY;\n            process.env.GPTKEY = \"\";\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-openai-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/openai')\n                        .auth(email, pwd)\n                        .send({ question: \"Provide 3 names for a dog\", context: \"you are an awesome assistant\", max_tokens: 100, temperature: 0, model: \"gpt-3.5-turbo\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body); }\n\n                            res.should.have.status(400);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.message).to.equal(\"Missing gptkey parameter\");\n\n                            process.env.GPTKEY = original_value;\n\n                            done();\n                        })\n\n\n                })\n            })\n        }).timeout(10000)\n\n\n        /**\n         * This test will be no longer available after merge with master because \n         * the profile section can no longer be modified via api.\n         */\n        // it('completionsWithProfileModified', (done) => {\n\n        //     var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        //     var pwd = \"pwd\";\n\n        //     userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n        //         projectService.create(\"test-openai-create\", savedUser._id).then((savedProject) => {\n\n        //             chai.request(server)\n        //                 .put('/projects/' + savedProject._id)\n        //                 .auth(email, pwd)\n        //                 .send({ profile: { quotes: { tokens: 400000, kbs: 100 } } })\n        //                 .end((err, res) => {\n        //                     if (log) { console.log(\"res.body: \", res.body); };\n        //                     console.log(\"res.body: \", res.body)\n\n        //                     chai.request(server)\n        //                         .post('/' + savedProject._id + '/openai')\n        //                         .auth(email, pwd)\n        //                         .send({ question: \"Provide 3 names for a dog\", context: \"you are an awesome assistant\", max_tokens: 100, temperature: 0, model: \"gpt-3.5-turbo\" })\n        //                         .end((err, res) => {\n                                    \n        //                             if (log) { console.log(\"res.body: \", res.body); }\n        //                             done();\n\n        //                         })\n\n\n        //                 })\n        //         })\n        //     })\n        // }).timeout(10000)\n\n    });\n\n});\n\n\n"
  },
  {
    "path": "test/projectRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.ADMIN_EMAIL = \"admin@tiledesk.com\";\n\nlet log = false;\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\nconst departmentService = require('../services/departmentService');\nconst routingConstants = require('../models/routingConstants');\nvar Group = require('../models/group');\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nlet operatingHours = '{\"1\":[{\"start\":\"09:00\",\"end\":\"13:00\"},{\"start\":\"14:00\",\"end\":\"18:00\"}],\"2\":[{\"start\":\"09:00\",\"end\":\"13:00\"},{\"start\":\"14:00\",\"end\":\"18:00\"}],\"3\":[{\"start\":\"09:00\",\"end\":\"11:00\"},{\"start\":\"14:00\",\"end\":\"18:00\"}],\"4\":[{\"start\":\"09:00\",\"end\":\"13:00\"},{\"start\":\"14:00\",\"end\":\"18:00\"}],\"5\":[{\"start\":\"09:00\",\"end\":\"13:00\"},{\"start\":\"14:00\",\"end\":\"18:00\"}],\"tzname\":\"Europe/Rome\"}';\nlet timeSlotsSample = {\n    \"819559cc\": {\n        name: \"Slot1\",\n        active: true,\n        //hours: \"{\\\"1\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"3\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"5\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"tzname\\\":\\\"America/Los_Angeles\\\"}\"\n        hours: \"{\\\"1\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"3\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"15:00\\\"},{\\\"start\\\":\\\"17:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"5\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"tzname\\\":\\\"Europe/Rome\\\"}\"\n    },\n    \"5d4368de\": {\n        name: \"Slot2\",\n        active: true,\n        hours: \"{\\\"0\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"6\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"tzname\\\":\\\"Europe/Rome\\\"}\"\n    },\n    \"2975ec45\": {\n        name: \"Slot3\",\n        active: false,\n        hours: \"{\\\"0\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"6\\\":[{\\\"start\\\":\\\"09:00\\\",\\\"end\\\":\\\"13:00\\\"},{\\\"start\\\":\\\"14:00\\\",\\\"end\\\":\\\"18:00\\\"}],\\\"tzname\\\":\\\"Europe/Rome\\\"}\"\n    }\n}\n\n\n\nchai.use(chaiHttp);\n\ndescribe('ProjectRoute', () => {\n\n    describe('/create', () => {\n\n        it('getAllProjectsWithSuperAdminCredential', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create-1\", savedUser._id).then(() => {\n                    projectService.create(\"test-project-create-2\", savedUser._id).then((savedProject2) => {\n\n                        chai.request(server)\n                        .post('/auth/signin')\n                        .send({ email: \"admin@tiledesk.com\", password: \"adminadmin\" })\n                        // .send({ email: email, password: pwd }) // con queste credenziali non si può fare la richiesta /projects/all\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"login with superadmin res.body: \", res.body) };\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(true);\n                            expect(res.body.token).not.equal(null);\n\n                            let superadmin_token = res.body.token;\n\n                            chai.request(server)\n                            .get('/projects/all')\n                            .set('Authorization', superadmin_token)\n                            .end((err, res) => {\n    \n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) {\n                                    console.log(\"res.body: \", res.body.length);\n                                    console.log(\"example: \", res.body[0]);\n                                }\n                                res.should.have.status(200);\n                                res.body.should.be.a('array');\n\n                                done()\n                            })\n                        })\n                    })\n                })\n            })\n        }).timeout(10000)\n        \n        it('updateProjectProfileWithSuperAdminCredential', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/auth/signin')\n                        .send({ email: \"admin@tiledesk.com\", password: \"adminadmin\" })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"login with superadmin res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.success).to.equal(true);\n                            expect(res.body.token).not.equal(null);\n\n                            let superadmin_token = res.body.token;\n\n                            chai.request(server)\n                                // .put('/projects/' + savedProject._id + \"/update\")\n                                .put('/projects/' + savedProject._id)\n                                .set('Authorization', superadmin_token)\n                                .send({ profile: { name: \"Custom\", quotes: { kbs: 1000 } } })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err); }\n                                    if (log) { console.log(\"update project profile res.body: \", res.body) };\n\n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.profile.name).to.equal(\"Custom\");\n                                    // expect(res.body.profile.quotes.kbs).to.equal(1000);\n\n                                    done();\n                                })\n                        })\n                })\n            })\n        }).timeout(10000)\n\n        it('denyUpdateProjectProfile', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .put('/projects/' + savedProject._id)\n                        // .put('/projects/' + savedProject._id + \"/update\")\n                        .auth(email, pwd)\n                        .send({ profile: { name: \"Custom\", quotes: { kbs: 1000 } } })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"update project profile res.body: \", res.body) };\n\n                            res.should.have.status(403);\n                            expect(res.body.success).to.equal(false);\n                            expect(res.body.error).to.equal(\"You don't have the permission required to modify the project profile\");\n                            done();\n                        })\n                })\n            })\n        }).timeout(10000)\n\n\n\n\n        it('updateProjectTimeSlots', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        // .put('/projects/' + savedProject._id + \"/update\")\n                        .put('/projects/' + savedProject._id)\n                        .auth(email, pwd)\n                        .send({ timeSlots: timeSlotsSample })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"update project time slots res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            done();\n                        })\n                })\n            })\n        }).timeout(10000)\n\n        it('isOpenTimeSlot', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        // .put('/projects/' + savedProject._id + \"/update\")\n                        .put('/projects/' + savedProject._id)\n                        .auth(email, pwd)\n                        .send({ timeSlots: timeSlotsSample })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"update project time slots res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            chai.request(server)\n                                .get('/projects/' + savedProject._id + '/isopen?timeSlot=819559cc')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body isopen: \", res.body) };\n\n                                    // Unable to do other checks due to currentTime change.\n                                    res.should.have.status(200);\n\n                                    done();\n\n                                })\n                        })\n\n\n                })\n            })\n        }).timeout(10000)\n\n        it('isOpenOperatingHours', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        // .put('/projects/' + savedProject._id + \"/update\")\n                        .put('/projects/' + savedProject._id)\n                        .auth(email, pwd)\n                        .send({ activeOperatingHours: true, operatingHours: operatingHours })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"update project time slots res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            chai.request(server)\n                                .get('/projects/' + savedProject._id + '/isopen')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body isopen: \", res.body) };\n\n                                    // Unable to do other checks due to currentTime change.\n                                    res.should.have.status(200);\n\n                                    done();\n\n                                })\n                        })\n\n\n                })\n            })\n        }).timeout(10000)\n\n        it('availableUsers', (done) => {\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .get('/projects/' + savedProject._id + '/users/availables')\n                        .auth(email, pwd)\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"res.body: \", res.body); }\n\n                            done();\n                        })\n                })\n            })\n        })\n\n        it('utcChecker', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        // .put('/projects/' + savedProject._id + \"/update\")\n                        .put('/projects/' + savedProject._id)\n                        .auth(email, pwd)\n                        .send({ timeSlots: timeSlotsSample })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"update project time slots res.body: \", res.body) };\n                            \n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n\n                            chai.request(server)\n                                .get('/projects/' + savedProject._id + '/isopen?timeSlot=819559cc')\n                                .auth(email, pwd)\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body isopen: \", res.body) };\n\n                                    // Unable to do other checks due to currentTime change.\n                                    res.should.have.status(200);\n\n                                    done();\n\n                                })\n                        })\n\n\n                })\n            })\n        }).timeout(10000)\n\n        it('departmentGroupAvailableUsers', (done) => {\n\n            var email = \"test-signup-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n\n            userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n                projectService.create(\"test-project-create\", savedUser._id).then((savedProject) => {\n\n                    chai.request(server)\n                        .post('/' + savedProject._id + '/groups')\n                        .auth(email, pwd)\n                        .send({ name: \"test-department-group\", members: [savedUser._id] })\n                        .end((err, res) => {\n\n                            if (err) { console.error(\"err: \", err) };\n                            if (log) { console.log(\"create group res.body: \", res.body); }\n                            let savedGroup = res.body;\n\n                            chai.request(server)\n                                .post('/' + savedProject._id + '/departments/')\n                                .auth(email, pwd)\n                                .send({ id_project: \"66977908249376002d57a434\", name: \"test-department\", routing: \"assigned\", id_group: savedGroup._id })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"savedDepartment: \", res.body); }\n                                    let savedDepartment = res.body;\n\n                                    chai.request(server)\n                                        .get('/projects/' + savedProject._id + '/users/availables/?department=' + savedDepartment._id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body: \", res.body); }\n\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('array');\n                                            expect(res.body.length).to.equal(1);\n                                            expect(res.body[0].id).to.equal(savedUser._id.toString());\n                                            expect(res.body[0].fullname).to.equal(savedUser.firstname + \" \" + savedUser.lastname);\n                                            expect(res.body[0].assigned_request).to.equal(0);\n                                            \n                                            done();\n                                        })\n\n                                })\n                        })\n                })\n            })\n        }).timeout(10000)\n\n    });\n\n});\n\n\n"
  },
  {
    "path": "test/projectService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nlet log = false;\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\n\nvar projectService = require('../services/projectService');\n\n\ndescribe('ProjectService()', function () {\n\n  var userid = \"5badfe5d553d1844ad654072\";\n\n\n  it('createProject', function (done) {\n\n     projectService.create(\"test1\", userid).then(function(savedProject) {\n        if (log) { console.log(\"createProject resolve\"); }\n         expect(savedProject.name).to.equal(\"test1\");\n        done();\n    }).catch(function(err) {\n        winston.error(\"test reject\", err);\n        assert.isNotOk(err,'Promise error');\n        done();\n    });\n  });\n});\n\n\n"
  },
  {
    "path": "test/projectUserRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'error';\nprocess.env.ADMIN_EMAIL = \"admin@tiledesk.com\";\n\nlet log = false;\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\n\n// chai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('ProjectUserRoute', () => {\n\n  describe('Delete', () => {\n\n    it('delete-project-user-and-remove-from-group', (done) => {\n\n      let email_owner = \"owner-signup-\" + Date.now() + \"@email.com\";\n      let email_agent = \"agent-signup-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email_owner, pwd, \"Owner Firstname\", \"Owner Lastname\").then((owner) => {\n        userService.signup(email_agent, pwd, \"Agent Firstname\", \"Agent Lastname\").then((agent) => {\n          projectService.create(\"test-project\", owner._id).then((project) => {\n\n            // Invite agent on the project\n            chai.request(server)\n              .post('/' + project._id + \"/project_users/invite\")\n              .auth(email_owner, pwd)\n              .set('content-type', 'application/json')\n              .send({ email: email_agent, role: \"agent\", userAvailable: false })\n              .end((err, res) => {\n\n                if (err) { console.error(\"err: \", err); }\n                if (log) { console.log(\"Invite agent to project res.body: \", res.body) };\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n                expect(res.body.id_user).to.equal(agent._id.toString());\n\n                let pu_id = res.body._id;\n\n                // Create group\n                chai.request(server)\n                  .post('/' + project._id + \"/groups\")\n                  .auth(email_owner, pwd)\n                  .set('content-type', 'application/json')\n                  .send({ name: \"test-group\", members: [owner._id, agent._id] })\n                  .end((err, res) => {\n\n                    if (err) { console.error(\"err: \", err); }\n                    if (log) { console.log(\"Create group res.body: \", res.body) };\n\n                    res.should.have.status(200);\n                    res.body.should.be.a('object');\n                    expect(res.body.name).to.equal(\"test-group\");\n                    expect(res.body.members.length).to.equal(2);\n                    expect(res.body.members[0]).to.equal(owner._id.toString())\n                    expect(res.body.members[1]).to.equal(agent._id.toString())\n\n                    let group_id = res.body._id;\n\n                    // Remove project user from project\n                    chai.request(server)\n                      .delete('/' + project._id + '/project_users/' + pu_id + '?soft=true')\n                      .auth(email_owner, pwd)\n                      .set('content-type', 'application/json')\n                      .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"Delete project user res.body: \", res.body) };\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        // Check group\n                        chai.request(server)\n                          .get('/' + project._id + '/groups/' + group_id)\n                          .auth(email_owner, pwd)\n                          .set('content-type', 'application/json')\n                          .end((err, res) => {\n                            if (err) { console.error(\"err: \", err); }\n                            if (log) { console.log(\"Check group res.body: \", res.body) };\n\n                            res.should.have.status(200);\n                            res.body.should.be.a('object');\n                            expect(res.body.members.length).to.equal(1);\n                            expect(res.body.members[0]).to.equal(owner._id.toString())\n\n                            done()\n                          })\n\n                      })\n                  })\n              })\n          })\n        })\n      })\n    }).timeout(5000);\n\n  })\n\n});"
  },
  {
    "path": "test/quoteManager.js",
    "content": "process.env.NODE_ENV = 'test';\nprocess.env.QUOTES_ENABLED = 'true';\n\nconst { QuoteManager } = require('../services/QuoteManager');\nconst pubModulesManager = require('../pubmodules/pubModulesManager');  // on constructor init is undefined beacusae pub module is loaded after\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\n\n\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nlet expect = require('chai').expect;\nlet assert = require('chai').assert;\n\nchai.use(chaiHttp);\n\n// MOCK\nconst projectMock = require('./mock/projectMock');\nconst requestMock = require('./mock/requestMock');\nconst messageMock = require('./mock/messageMock');\nconst emailMock = require('./mock/emailMock');\nconst { MockTdCache } = require('./mock/tdCacheMock');\nconst mockTdCache = new MockTdCache();\n\nlet log = false;\n\n// CONNECT REDIS - CHECK IT\nconst { TdCache } = require('../utils/TdCache');\nlet tdCache = new TdCache({\n    host: process.env.CACHE_REDIS_HOST || '127.0.0.1',\n    port: process.env.CACHE_REDIS_PORT || '6379',\n    password: process.env.CACHE_REDIS_PASSWORD || undefined\n});\n\ntdCache.connect();\n// var redis = require('redis')\n// var redis_client;\n\n// const dateList = [\n//     \"2023-10-17T08:45:54.058Z\",\n//     \"2023-10-20T08:45:54.058Z\",\n//     \"2023-10-28T08:45:54.058Z\",\n//     \"2023-10-31T08:45:54.058Z\",\n//     \"2023-11-19T08:45:54.058Z\",\n//     \"2023-11-22T08:45:54.058Z\",\n//     \"2023-11-27T08:45:54.058Z\",\n//     \"2023-12-01T08:45:54.058Z\",\n//     \"2023-12-20T08:45:54.058Z\",\n//     \"2023-12-21T08:45:54.058Z\"\n// ]\n\nconst dateList = [\n    \"2023-10-24T08:45:54.058Z\",\n    \"2023-10-25T08:45:54.058Z\",\n    \"2023-10-26T08:45:54.058Z\",\n    \"2023-10-27T08:45:54.058Z\",\n    \"2023-10-28T08:45:54.058Z\",\n    \"2023-10-29T08:45:54.058Z\"\n]\n\nconst dateListStress = [\n    \"2024-02-05T10:30:00.058Z\",\n    \"2024-03-05T10:30:00.058Z\",\n    \"2024-04-05T10:30:00.058Z\",\n    \"2024-05-05T10:30:00.058Z\",\n    \"2024-06-05T10:30:00.058Z\",\n    \"2024-07-05T10:30:00.058Z\",\n    \"2024-08-05T10:30:00.058Z\",\n    \"2024-09-05T10:30:00.058Z\",\n    \"2024-10-05T10:30:00.058Z\",\n    \"2024-11-05T10:30:00.058Z\",\n    \"2024-12-05T10:30:00.058Z\",\n    \"2025-01-05T10:30:00.058Z\",\n    \"2025-02-05T10:30:00.058Z\",\n    \"2025-03-05T10:30:00.058Z\"\n]\n\nconst expectedSlotStarts = [\n    \"1/31/2024\",\n    \"2/29/2024\",\n    \"3/31/2024\",\n    \"4/30/2024\",\n    \"5/31/2024\",\n    \"6/30/2024\",\n    \"7/31/2024\",\n    \"8/31/2024\",\n    \"9/30/2024\",\n    \"10/31/2024\",\n    \"11/30/2024\",\n    \"12/31/2024\",\n    \"1/31/2025\",\n    \"2/28/2025\",\n    \"3/31/2025\"\n]\n\nconst _expectedSlotStarts_PRE = [\n    \"31/01/2024\",\n    \"29/02/2024\",\n    \"31/03/2024\",\n    \"30/04/2024\",\n    \"31/05/2024\",\n    \"30/06/2024\",\n    \"31/07/2024\",\n    \"31/08/2024\",\n    \"30/09/2024\",\n    \"31/10/2024\",\n    \"30/11/2024\",\n    \"31/12/2024\",\n    \"31/01/2025\",\n    \"28/02/2025\",\n    \"31/03/2025\"\n]\n// connectRedis();\n\n// function connectRedis() {\n//     console.log(\">>> connectRedis\")\n//     redis_client = redis.createClient({\n//         host: \"127.0.0.1\",\n//         port: 6379,\n//     });\n\n//     redis_client.on('error', err => {\n//         console.log('(quoteManager TEST) Connect Redis Error ' + err);\n//     })\n\n//     redis_client.on('ready', () => {\n//         console.log(\"(quoteManager TEST) Redis ready!\")\n//     })\n// }\n\n\n\n// let cacheClient = undefined;\n// if (pubModulesManager.cache) {\n//     cacheClient = pubModulesManager.cache._cache._cache;  //_cache._cache to jump directly to redis modules without cacheoose wrapper (don't support await)\n// }\n\n// let tdCache = undefined;\n// if (cacheClient) {\n//     tdCache = cacheClient;\n//     console.log(\"using 'cacheClient' for the test\")\n// } else if (redis_client) {\n//     tdCache = redis_client\n//     console.log(\"using 'redis_client' for the test\")\n// } else {\n//     tdCache = mockTdCache;\n//     console.log(\"using 'mockTdCache' for the test\")\n// }\n\nlet quoteManager = new QuoteManager({ tdCache: tdCache });\nquoteManager.start();\n\n\ndescribe('QuoteManager', function () {\n\n\n    it('incrementRequestsCountStress',  (done) => {\n        /**\n         * For this test the subscription starts on 31 Genuary.\n         * Slots must start on 31 Gen, 28/29 Feb, 31 Mar, 30 Apr, ...\n         */\n        let mockProject = projectMock.mockProjectPremiumPlan2;\n        let mockRequest = requestMock.requestMock;\n\n        if (log) { console.log(\"Starting stress test with one year long cycle of renewals\") }\n\n        let i = 0;\n        async function test(date) {\n            \n            mockRequest.createdAt = new Date(date);\n            let key_incremented = await quoteManager.incrementRequestsCount(mockProject, mockRequest);\n            if (log) { console.log(\"request_date: \", mockRequest.createdAt, \"key_incremented: \", key_incremented); }\n            \n            let slot_start = key_incremented.substring(key_incremented.lastIndexOf(':') + 1)\n            expect(slot_start).to.equal(expectedSlotStarts[i]);\n\n            i += 1;\n            if (i < dateListStress.length) {\n                setTimeout(() => {\n                    test(dateListStress[i])\n                }, 250);\n            } else {\n                if (log) { console.log(\"All dates tested successfully.\"); }\n                done();\n            }\n        }\n        test(dateListStress[0]);\n\n    }).timeout(10000)\n\n    it('incrementRequestsCount', async function () {\n\n        let mockProject = projectMock.mockProjectBasicPlan;\n        let mockRequest = requestMock.requestMock;\n\n        mockRequest.createdAt = new Date(dateList[0]);\n\n        let initial_quote = await quoteManager.getCurrentQuote(mockProject, mockRequest, 'requests');\n        if (log) { console.log(\"[Quote Test] initial_quote: \", initial_quote); }\n\n        let key_incremented = await quoteManager.incrementRequestsCount(mockProject, mockRequest);\n        if (log) { console.log(\"[Quote Test] key_incremented: \", key_incremented); }\n\n        let final_quote = await quoteManager.getCurrentQuote(mockProject, mockRequest, 'requests');\n        if (log) { console.log(\"[Quote Test] final_quote: \", final_quote); }\n\n        expect(key_incremented).to.equal(\"quotes:requests:64e36f5dbf72263f7c059999:10/20/2023\");\n        expect(final_quote).to.equal(initial_quote + 1);\n    })\n\n    it('incrementMessagesCount', async function () {\n        let mockProject = projectMock.mockProjectSandboxPlan;\n        let mockMessage = messageMock.messageMock;\n\n        mockMessage.createdAt = new Date();\n\n        let initial_quote = await quoteManager.getCurrentQuote(mockProject, mockMessage, 'messages');\n        if (log) { console.log(\"[Quote Test] initial_quote: \", initial_quote); }\n\n        let key_incremented = await quoteManager.incrementMessagesCount(mockProject, mockMessage);\n        if (log) { console.log(\"[Quote Test] key_incremented: \", key_incremented); }\n\n        let final_quote = await quoteManager.getCurrentQuote(mockProject, mockMessage, 'messages');\n        if (log) { console.log(\"[Quote Test] current quote: \", final_quote); }\n\n        //expect(key_incremented).to.equal(\"quotes:messages:64e36f5dbf72263f7c059999:20/10/2023\");\n        expect(final_quote).to.equal(initial_quote + 1);\n\n    })\n\n    it('incrementEmailCount', async function () {\n        let mockProject = projectMock.mockProjectSandboxPlan;\n        let mockEmail = emailMock.emailMock;\n\n        let result = await quoteManager.checkQuote(mockProject, mockEmail, 'email');\n        if (log) { console.log(\"checkQuote result: \", result) }\n\n        let initial_quote = await quoteManager.getCurrentQuote(mockProject, mockEmail, 'email');\n        if (log) { console.log(\"[Quote Test] initial_quote: \", initial_quote); }\n\n        let key_incremented = await quoteManager.incrementEmailCount(mockProject, mockEmail);\n        if (log) { console.log(\"[Quote Test] key_incremented: \", key_incremented); }\n\n        let final_quote = await quoteManager.getCurrentQuote(mockProject, mockEmail, 'email');\n        if (log) { console.log(\"[Quote Test] current quote: \", final_quote); }\n\n        expect(key_incremented).to.equal(\"quotes:email:64e36f5dbf72263f7c059999:10/20/2023\");\n        expect(final_quote).to.equal(initial_quote + 1);\n\n    })\n\n    it('sendEmailDirect', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-send-email-direct\", savedUser._id).then(function (savedProject) {\n                chai.request(server)\n                    .post('/' + savedProject._id + '/emails/internal/send')\n                    .auth(email, pwd)\n                    .send({ to: \"giovanni.troisiub@gmail.com\", text: \"Hello\", subject: \"HelloSub\", replyto: \"giovanni.troisiub@gmail.com\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"email internal send err: \", err); }\n                        if (log) { console.log(\"email internal send res.body\", res.body); }\n                        \n                        done();\n                    });\n            });\n        });\n\n    })\n\n    it('incrementTokensCount', (done) => {\n\n        var email = \"test-quote-\" + Date.now() + \"@email.com\"; \n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n            projectService.create(\"quote-project\", savedUser._id).then((savedProject) => {\n\n                let createdAt = new Date();\n                createdAt.setDate(createdAt.getDate() + 1)\n\n                chai.request(server)\n                    .post('/' + savedProject._id + \"/openai/quotes\")\n                    .auth(email, pwd)\n                    .send({ createdAt: createdAt , tokens: 128, multiplier: 25 })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body )};\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        const options = {\n                            day: '2-digit',\n                            month: '2-digit',\n                            year: 'numeric',\n                        };                          \n                        let date = new Date().toLocaleDateString(undefined, options);\n                    \n                        let key = \"quotes:tokens:\" + savedProject._id + \":\" + date;\n                        let message_resp = \"value incremented for key \" + key;\n                        // CHECK IT!!!!\n                        //expect(res.body.message).to.equal(message_resp);\n                        //expect(res.body.key).to.equal(key);\n                        let expected_quote = 128 * 25;\n                        expect(res.body.currentQuote).to.equal(expected_quote);\n\n\n                        done();\n                    })\n            })\n        })\n\n\n    })\n\n\n\n\n    // it('incrementRequestCountMulti', async function () {\n    //     let mockProject = projectMock.mockProjectSandboxPlan;\n    //     let mockRequest = requestMock.requestMock;\n\n    //     let quoteManager = new QuoteManager({ project: mockProject, tdCache: tdCache });\n\n    //     for (let date of dateList) {\n    //         mockRequest.createdAt = new Date(date);\n    //         let result = await quoteManager.incrementRequestsCount(mockRequest);\n    //         console.log(\"result: \", result);\n    //         console.log(\"\\n\\n\");\n    //     }\n\n    // })\n\n    // it('incrementRequestCountLimitReached', async function () {\n\n    //     let mockProject = projectMock.mockProjectSandboxPlan;\n    //     let mockRequest = requestMock.requestMock;\n\n    //     // for the test the limit is fixed to 5\n\n    //     let i;\n    //     let quoteManager = new QuoteManager({ project: mockProject, tdCache: tdCache });\n\n    //     for (i = 0; i < 5; i++) {\n    //         mockRequest.createdAt = new Date(dateList[i]);\n    //         await quoteManager.incrementRequestsCount(mockRequest);\n    //     }\n\n    //     mockRequest.createdAt = new Date(dateList[i]);\n    //     let result = await quoteManager.incrementRequestsCount(mockRequest);\n    //     console.log(\"result: \", result);\n\n\n    // })\n\n    // it('getCurrentCount', async function() {\n    //     let mockProject = projectMock.mockProjectSandboxPlan;\n    //     let mockRequest = requestMock.requestMock; \n\n    //     let quoteManager = new QuoteManager({ project: mockProject, tdCache: mockTdCache } );\n\n    //     for (let date of dateList) {\n    //         mockRequest.createdAt = new Date(date);\n    //         let result = await quoteManager.incrementRequestCount(mockRequest);\n    //         console.log(\"result: \", result);\n    //     }\n\n    //     let today = new Date('2023-12-22T08:45:54.058Z');\n    //     let quote = await quoteManager.getCurrentQuote(today, 'requests');\n    //     console.log(\"request quote: \", quote)\n\n\n    // })\n})"
  },
  {
    "path": "test/requestRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar projectService = require('../services/projectService');\nvar requestService = require('../services/requestService');\nvar userService = require('../services/userService');\nvar leadService = require('../services/leadService');\nvar faqService = require('../services/faqService');\n\nvar Department = require('../models/department');\nvar winston = require('../config/winston');\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n// require('../services/mongoose-cache-fn')(mongoose);\n// chai.config.includeStack = true;\n\nlet log = false;\n\nchai.use(chaiHttp);\n\ndescribe('RequestRoute', () => {\n\n  // mocha test/requestRoute.js  --grep 'createSimple'\n  it('createSimple', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \",  err); }\n            if (log) { console.log(\"res.body\",  res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            //expect(res.body.snapshot.agents.length).to.equal(1);\n            // res.body.should.have.property('request_id').eql('request_id');\n            // res.body.should.have.property('requester_id').eql('requester_id');\n            res.body.should.have.property('first_text').eql('first_text');\n            res.body.should.have.property('id_project').eql(savedProject._id.toString());\n            res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n\n            // res.body.should.have.property('messages_count').gt(0);\n\n            res.body.should.have.property('status').eql(200);\n\n            // res.body.should.have.property('agents').eql(savedUser._id);\n            //expect(res.body.snapshot.agents.length).to.equal(1);\n            expect(res.body.participants.length).to.equal(1);\n\n            expect(res.body.participantsAgents.length).to.equal(1);\n            expect(res.body.participantsBots).to.have.lengthOf(0);\n            expect(res.body.hasBot).to.equal(false);\n\n            res.body.should.have.property('department').not.eql(null);\n            // res.body.should.have.property('lead').eql(undefined);\n\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'create-simple-new-note'\n  it('create-simple-new-note', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body\",  res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            let request_id = res.body.request_id;\n            chai.request(server)\n                .post('/' + savedProject._id + '/requests/' + request_id + \"/notes\")\n                .auth(email, pwd)\n                .send({ text: \"test note 1\"})\n                .end((err, res) => {\n\n                  if (err) { console.error(\"err: \", err); }\n                  if (log) { console.log(\"res.body\",  res.body); }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.notes.length).to.equal(1);\n                  expect(res.body.notes[0].text).to.equal(\"test note 1\");\n                  expect(res.body.notes[0].createdBy).to.equal(savedUser._id.toString());\n                  \n                  done();\n                  // Project_user.findOneAndUpdate({id_project: savedProject._id, id_user: savedUser._id }, { role: RoleConstants.AGENT }, function(err, savedProject_user){\n                  //   done();\n                  // })\n                })\n\n          });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'createSimpleAndCloseForDuration'\n  it('createSimpleAndCloseForDuration', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err: \",  err); }\n            if (log) { console.log(\"res.body\",  res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            setTimeout(() => {\n\n              chai.request(server)\n                  .put('/' + savedProject._id + '/requests/' + res.body.request_id + '/close')\n                  .auth(email, pwd)\n                  .send()\n                  .end((err, res) => {\n  \n                    if (err) { console.error(\"err: \",  err); }\n                    if (log) { \n                      console.log(\"res.body\",  res.body); \n                      console.log(\"request duration: \", res.body.duration)\n                    }\n\n                    res.body.should.have.property('duration');\n                    res.body.duration.should.be.above(2000);\n  \n                    done();\n                  })\n\n            }, 2000)\n\n          });\n      });\n    });\n  }).timeout(5000);\n\n\n  // mocha test/requestRoute.js  --grep 'createUpperCaseEmail'\n  it('createUpperCaseEmail', function (done) {\n    // this.timeout(10000);\n\n    var now = Date.now();\n    var email = \"test-REQUEST-create-\" + now + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id).then(function (savedProject) {\n\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(\"test-request-create-\" + now + \"@email.com\", pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n            \n            if (err) { console.error(\"err: \",  err); }\n            if (log) { console.log(\"res.body\",  res.body); }\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n\n            //expect(res.body.snapshot.agents.length).to.equal(1);\n            // res.body.should.have.property('request_id').eql('request_id');\n            // res.body.should.have.property('requester_id').eql('requester_id');\n            res.body.should.have.property('first_text').eql('first_text');\n            res.body.should.have.property('id_project').eql(savedProject._id.toString());\n            res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n\n            // res.body.should.have.property('messages_count').gt(0);\n\n            res.body.should.have.property('status').eql(200);\n\n            // res.body.should.have.property('agents').eql(savedUser._id);\n            //expect(res.body.snapshot.agents.length).to.equal(1);\n            expect(res.body.participants.length).to.equal(1);\n\n            expect(res.body.participantsAgents.length).to.equal(1);\n            expect(res.body.participantsBots).to.have.lengthOf(0);\n            expect(res.body.hasBot).to.equal(false);\n\n            res.body.should.have.property('department').not.eql(null);\n            // res.body.should.have.property('lead').eql(undefined);\n\n\n            done();\n          });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getbyid'\n  it('getbyid', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n\n        // leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n        // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n        //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n        requestService.createWithIdAndRequester(\"request_requestroute-getbyid-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          winston.debug(\"resolve\", savedRequest.toObject());\n\n\n          chai.request(server)\n            .get('/' + savedProject._id + '/requests/' + savedRequest.request_id)\n            .auth(email, pwd)\n            .end(function (err, res) {\n              \n              if (err) { console.error(\"err: \",  err); }\n              if (log) { console.log(\"res.body\",  res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n\n              res.body.should.have.property('department').not.eql(null);\n              // res.body.should.have.property('lead').eql(null);\n              res.body.should.have.property('request_id').eql(\"request_requestroute-getbyid-\" + now);\n              res.body.should.have.property('requester').not.eql(null);\n\n              expect(res.body.participantsAgents.length).to.equal(1);\n              expect(res.body.participantsBots).to.have.lengthOf(0);\n              expect(res.body.hasBot).to.equal(false);\n\n              expect(res.body.participatingAgents.length).to.equal(1);\n              expect(res.body.participatingBots.length).to.equal(0);\n\n              expect(res.body.participatingAgents.length).to.equal(1);\n              expect(res.body.participatingBots).to.have.lengthOf(0);\n\n              expect(res.body.requester._id).to.not.equal(savedProjectAndPU.project_user._id);\n              expect(res.body.requester.isAuthenticated).to.equal(true);\n\n              //expect(res.body.snapshot.agents).to.equal(undefined);\n\n              done();\n            });\n          // .catch(function(err) {\n          //     console.log(\"test reject\", err);\n          //     assert.isNotOk(err,'Promise error');\n          //     done();\n          // });\n          // });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getbyidWithPartecipatingBots'\n  it('getbyidWithPartecipatingBots', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n\n        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function (savedBot) {\n\n\n\n          Department.findOneAndUpdate({ id_project: savedProject._id, default: true }, { id_bot: savedBot._id }, { new: true, upsert: false }, function (err, updatedDepartment) {\n\n            winston.error(\"err\", err);\n            winston.info(\"updatedDepartment\", updatedDepartment.toObject());\n            var now = Date.now();\n\n\n            // leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function(createdLead) {\n            // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n            //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            requestService.createWithIdAndRequester(\"request_requestroute-getbyidWithPartecipatingBots-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n              winston.debug(\"resolve\", savedRequest.toObject());\n\n\n              chai.request(server)\n                .get('/' + savedProject._id + '/requests/' + savedRequest.request_id)\n                .auth(email, pwd)\n                .end(function (err, res) {\n                  \n                  if (err) { console.error(\"err: \",  err); }\n                  if (log) { console.log(\"res.body\",  res.body); }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n\n                  res.body.should.have.property('department').not.eql(null);\n\n                  // res.body.should.have.property('lead').eql(null);\n                  res.body.should.have.property('request_id').eql(\"request_requestroute-getbyidWithPartecipatingBots-\" + now);\n                  res.body.should.have.property('requester').not.eql(null);\n                  expect(res.body.requester._id).to.not.equal(savedProjectAndPU.project_user._id);\n\n                  expect(res.body.participatingAgents.length).to.equal(0);\n                  expect(res.body.participatingBots.length).to.equal(1);\n\n                  expect(res.body.participantsAgents.length).to.equal(0);\n                  expect(res.body.participantsBots).to.have.lengthOf(1);\n                  expect(res.body.hasBot).to.equal(true);\n\n                  //expect(res.body.snapshot.agents).to.equal(undefined);\n                  expect(res.body.department.hasBot).to.equal(true);\n\n                  done();\n                });\n              // .catch(function(err) {\n              //     console.log(\"test reject\", err);\n              //     assert.isNotOk(err,'Promise error');\n              //     done();\n              // });\n              // });\n            });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallSimple'\n  it('getallSimple', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-getallsimple-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      if (log) { console.log(\"savedUser\", savedUser); }\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        if (log) { console.log(\"savedProjectAndPU\", savedProjectAndPU); }\n\n        leadService.createIfNotExists(\"leadfullname\", \"email-getallSimple@email.com\", savedProject._id).then(function (createdLead) {\n\n          if (log) { console.log(\"createdLead\", createdLead); }\n\n          var now = Date.now();\n\n          var new_request = {\n            request_id: \"request_id-getallSimple-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n\n          requestService.create(new_request).then(function (savedRequest) {\n\n            if (log) { console.log(\"savedRequest\", savedRequest); }\n\n            // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n            //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            // requestService.createWithIdAndRequester(\"request_id1\", savedProjectAndPU.project_user._id, null,savedProject._id, \"first_text\").then(function(savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                expect(res.body.requests[0].department).to.not.equal(null);\n                expect(res.body.requests[0].requester).to.not.equal(null);\n                if (log) { console.log(\"res.body.requests[0].requester\", res.body.requests[0].requester); }\n\n                expect(res.body.requests[0].requester.id_user.firstname).to.equal(\"Test Firstname\");\n\n                expect(res.body.requests[0].participantsAgents.length).to.equal(1);\n                expect(res.body.requests[0].participantsBots).to.have.lengthOf(0);\n                expect(res.body.requests[0].hasBot).to.equal(false);\n                // expect(res.body.requests[0].snapshot).to.not.equal(undefined);\n                // expect(res.body.requests[0].snapshot.department.name).to.not.equal(null);\n                // expect(res.body.requests[0].snapshot.agents.length).to.equal(1);\n                // expect(res.body.requests[0].snapshot.availableAgentsCount).to.equal(1);\n                // expect(res.body.requests[0].snapshot.lead.fullname).to.equal(\"leadfullname\");\n                // expect(res.body.requests[0].snapshot.requester.role).to.equal(\"owner\");\n                // expect(res.body.requests[0].snapshot.agents).to.equal(undefined);\n\n                // expect(res.body.requests[0].participatingAgents.length).to.equal(1);        \n                // expect(res.body.requests[0].participatingBots.length).to.equal(0);\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallNoPopulate'\n  it('getallNoPopulate', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId-getallNoPopulate\", savedUser._id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n          //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n          var now = Date.now();\n\n          var request = {\n            request_id: \"request_getallNoPopulate-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n\n          requestService.create(request).then(function (savedRequest) {\n\n            // requestService.createWithIdAndRequester(\"request_id1\", savedProjectAndPU.project_user._id, null,savedProject._id, \"first_text\").then(function(savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/?no_populate=true')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n                // assert.isString(res.body.requests[0].department, 'order placed');\n\n                expect(res.body.requests[0].department).to.be.a('string');\n                expect(res.body.requests[0].requester).to.be.a('string');\n                // console.log(\"res.body.requests[0].requester\",  res.body.requests[0].requester);\n                // expect(res.body.requests[0].requester.id_user.firstname).to.equal(\"Test Firstname\");\n\n                if (log) { console.log(\"res.body.requests[0].participantsAgents\", res.body.requests[0].participantsAgents); }\n                expect(res.body.requests[0].participantsAgents).to.have.lengthOf(1);\n                expect(res.body.requests[0].participantsAgents[0]).to.equal(savedUser._id.toString());\n                expect(res.body.requests[0].participantsBots).to.have.lengthOf(0);\n                expect(res.body.requests[0].hasBot).to.equal(false);\n\n                // expect(res.body.requests[0].snapshot).to.not.equal(undefined);\n                // expect(res.body.requests[0].snapshot.department.name).to.not.equal(null);\n                // expect(res.body.requests[0].snapshot.agents).to.equal(undefined);\n                // expect(res.body.requests[0].snapshot.agents.length).to.equal(1);\n                // expect(res.body.requests[0].test).to.not.equal(undefined);\n                // expect(res.body.requests[0].participatingAgents.length).to.equal(1);        \n                // expect(res.body.requests[0].participatingBots.length).to.equal(0);\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallSimple'\n  it('getallFilter-snap_department_routing', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-getallfilter-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      if (log) { console.log(\"savedUser\", savedUser); }\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        if (log) { console.log(\"savedProjectAndPU\", savedProjectAndPU); }\n\n        leadService.createIfNotExists(\"leadfullname\", \"email-getallfilter@email.com\", savedProject._id).then(function (createdLead) {\n\n          if (log) { console.log(\"createdLead\", createdLead); }\n          var now = Date.now();\n\n\n          var new_request = {\n            request_id: \"request_id-getallFilter-snap_department_routing-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n\n          requestService.create(new_request).then(function (savedRequest) {\n\n            if (log) { console.log(\"savedRequest\", savedRequest); }\n\n            // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n            //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            // requestService.createWithIdAndRequester(\"request_id1\", savedProjectAndPU.project_user._id, null,savedProject._id, \"first_text\").then(function(savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/?snap_department_routing=assigned')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                expect(res.body.requests[0].department).to.not.equal(null);\n                expect(res.body.requests[0].requester).to.not.equal(null);\n                if (log) { console.log(\"res.body.requests[0].requester\", res.body.requests[0].requester); }\n\n                expect(res.body.requests[0].requester.id_user.firstname).to.equal(\"Test Firstname\");\n\n                expect(res.body.requests[0].participantsAgents.length).to.equal(1);\n                expect(res.body.requests[0].participantsBots).to.have.lengthOf(0);\n                expect(res.body.requests[0].hasBot).to.equal(false);\n                // expect(res.body.requests[0].snapshot).to.not.equal(undefined);\n                // expect(res.body.requests[0].snapshot.department.name).to.not.equal(null);\n                // expect(res.body.requests[0].snapshot.agents.length).to.equal(1);\n                // expect(res.body.requests[0].snapshot.availableAgentsCount).to.equal(1);\n                // expect(res.body.requests[0].snapshot.lead.fullname).to.equal(\"leadfullname\");\n                // expect(res.body.requests[0].snapshot.requester.role).to.equal(\"owner\");\n                // expect(res.body.requests[0].snapshot.agents).to.equal(undefined);\n                // expect(res.body.requests[0].participatingAgents.length).to.equal(1);        \n                // expect(res.body.requests[0].participatingBots.length).to.equal(0);\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallFilter-snap_department_default'\n  it('getallFilter-snap_department_default', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-getallfilter-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      if (log) { console.log(\"savedUser\", savedUser); }\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        if (log) { console.log(\"savedProjectAndPU\", savedProjectAndPU); }\n\n        leadService.createIfNotExists(\"leadfullname\", \"email-getallfilter@email.com\", savedProject._id).then(function (createdLead) {\n\n          if (log) { console.log(\"createdLead\", createdLead); }\n\n          var now = Date.now();\n\n          var new_request = {\n            request_id: \"request_id-getallFilter-snap_department_routing-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n\n          requestService.create(new_request).then(function (savedRequest) {\n\n            if (log) { console.log(\"savedRequest\", savedRequest); }\n\n            // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n            //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            // requestService.createWithIdAndRequester(\"request_id1\", savedProjectAndPU.project_user._id, null,savedProject._id, \"first_text\").then(function(savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/?snap_department_default=true')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                expect(res.body.requests[0].department).to.not.equal(null);\n                expect(res.body.requests[0].requester).to.not.equal(null);\n                if (log) { console.log(\"res.body.requests[0].requester\", res.body.requests[0].requester); }\n\n                expect(res.body.requests[0].requester.id_user.firstname).to.equal(\"Test Firstname\");\n\n                expect(res.body.requests[0].participantsAgents.length).to.equal(1);\n                expect(res.body.requests[0].participantsBots).to.have.lengthOf(0);\n                expect(res.body.requests[0].hasBot).to.equal(false);\n                // expect(res.body.requests[0].snapshot).to.not.equal(undefined);\n                // expect(res.body.requests[0].snapshot.department.name).to.not.equal(null);\n                // expect(res.body.requests[0].snapshot.agents.length).to.equal(1);\n                // expect(res.body.requests[0].snapshot.availableAgentsCount).to.equal(1);\n                // expect(res.body.requests[0].snapshot.lead.fullname).to.equal(\"leadfullname\");\n                // expect(res.body.requests[0].snapshot.requester.role).to.equal(\"owner\");\n                // expect(res.body.requests[0].snapshot.agents).to.equal(undefined);\n                // expect(res.body.requests[0].participatingAgents.length).to.equal(1);        \n                // expect(res.body.requests[0].participatingBots.length).to.equal(0);\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'snap_department_id_bot_exists'\n  it('getallFilter-snap_department_id_bot_exists', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-getallfilter-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      if (log) { console.log(\"savedUser\", savedUser); }\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n\n        if (log) { console.log(\"savedProjectAndPU\", savedProjectAndPU); }\n\n        leadService.createIfNotExists(\"leadfullname\", \"email-getallfilter@email.com\", savedProject._id).then(function (createdLead) {\n\n          if (log) { console.log(\"createdLead\", createdLead); }\n\n          var now = Date.now();\n\n          var new_request = {\n            request_id: \"request_id-getallFilter-snap_department_id_bot_exists-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n\n          requestService.create(new_request).then(function (savedRequest) {\n\n            if (log) { console.log(\"savedRequest\", savedRequest); }\n\n            // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n            //  requestService.createWithId(\"request_id1\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            // requestService.createWithIdAndRequester(\"request_id1\", savedProjectAndPU.project_user._id, null,savedProject._id, \"first_text\").then(function(savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/?snap_department_id_bot_exists=false')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                expect(res.body.requests[0].department).to.not.equal(null);\n                expect(res.body.requests[0].requester).to.not.equal(null);\n                if (log) { console.log(\"res.body.requests[0].requester\", res.body.requests[0].requester); }\n\n                expect(res.body.requests[0].requester.id_user.firstname).to.equal(\"Test Firstname\");\n\n                expect(res.body.requests[0].participantsAgents.length).to.equal(1);\n                expect(res.body.requests[0].participantsBots).to.have.lengthOf(0);\n                expect(res.body.requests[0].hasBot).to.equal(false);\n                // expect(res.body.requests[0].snapshot).to.not.equal(undefined);\n                // expect(res.body.requests[0].snapshot.department.name).to.not.equal(null);\n                // expect(res.body.requests[0].snapshot.agents.length).to.equal(1);\n                // expect(res.body.requests[0].snapshot.availableAgentsCount).to.equal(1);\n                // expect(res.body.requests[0].snapshot.lead.fullname).to.equal(\"leadfullname\");\n                // expect(res.body.requests[0].snapshot.requester.role).to.equal(\"owner\");\n\n                // expect(res.body.requests[0].snapshot.agents).to.equal(undefined);\n                // expect(res.body.requests[0].participatingAgents.length).to.equal(1);        \n                // expect(res.body.requests[0].participatingBots.length).to.equal(0);\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallcsv'\n  it('getallcsv', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.createAndReturnProjectAndProjectUser(\"getallcsv\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n\n          winston.info(\"createdLead\", createdLead.toObject());\n          // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) {\n          var now = Date.now();\n\n          requestService.create({\n            request_id: \"request_id-getallcsv-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id, id_project: savedProject._id,\n            first_text: \"first_text\", tags: [{ tag: \"tag1\" }, { tag: \"tag2\" }]\n          }).then(function (savedRequest) {\n            winston.info(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/csv/')\n              .auth(email, pwd)\n              .end(function (err, res) {\n\n                if (err) { console.error(\"err: \", err); }\n                if (log) { console.log(\"res.text\", res.text); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'getallWithLoLead'\n  it('getallWithLoLead', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      projectService.createAndReturnProjectAndProjectUser(\"getallcsv\", savedUser._id).then(function (savedProjectAndPU) {\n\n        var savedProject = savedProjectAndPU.project;\n        leadService.createIfNotExists(\"request_id1-getallWithLoLead\", \"email@getallWithLoLead.com\", savedProject._id).then(function (createdLead) {\n\n          winston.info(\"createdLead\", createdLead.toObject());\n          // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes, subject, preflight, channel, location) {\n          var now = Date.now();\n\n          requestService.createWithIdAndRequester(\"request_id-getallWithLoLead-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n            winston.debug(\"resolve\", savedRequest.toObject());\n\n\n            chai.request(server)\n              .get('/' + savedProject._id + '/requests/')\n              .auth(email, pwd)\n              .end(function (err, res) {\n                \n                if (err) { console.error(\"err: \",  err); }\n                if (log) { console.log(\"res.body\",  res.body); }\n\n                res.should.have.status(200);\n                res.body.should.be.a('object');\n                expect(res.body.requests[0].department).to.not.equal(null);\n                expect(res.body.requests[0].lead).to.not.equal(null);\n\n                //.agents).to.equal(undefined);\n\n                done();\n              });\n            // .catch(function(err) {\n            //     console.log(\"test reject\", err);\n            //     assert.isNotOk(err,'Promise error');\n            //     done();\n            // });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'countConversations'\n  it('countConversations', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.log(\"err: \", err) };\n            if (log) { console.log(\"res.body: \", res.body) };\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            \n            chai.request(server)\n                .get('/' + savedProject._id + '/requests/count?conversation_quota=true')\n                .auth(email, pwd)\n                .end((err, res) => {\n\n                  if (err) { console.log(\"err: \", err) };\n                  if (log) { console.log(\"res.body: \", res.body) };\n\n                  res.should.have.status(200);\n\n                  done();\n                })\n        \n          });\n      });\n    });\n  });\n\n\n  // mocha test/requestRoute.js  --grep 'exludeDraftConversations'\n  it('exludeDraftConversations', (done) => {\n\n    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test Lastname\").then((savedUser) => {\n      projectService.createAndReturnProjectAndProjectUser(\"test-draft-conversation\", savedUser._id).then((savedProjectAndPU) => {\n        let savedProject = savedProjectAndPU.project;\n        let savedPU = savedProjectAndPU.project_user;\n        leadService.createIfNotExists(\"Lead Fullname\", \"email@test.com\", savedProject._id).then((createdLead) => {\n          let now = Date.now();\n          let request = {\n            request_id: \"request_id-exludeDraftConversations-\" + now, \n            project_user_id: savedPU._id,\n            lead_id: createdLead._id,\n            id_project: savedProject._id,\n            first_text: \"first_text\",\n            lead: createdLead,\n            requester: savedPU,\n            attributes: { sourcePage: \"https://widget-pre.tiledesk.com/v2/index.html?tiledesk_projectid=5ce3d1ceb25ad30017279999&td_draft=true\" }\n          }\n\n          requestService.create(request).then(async (savedRequest) => {\n            \n            // Case 1 - request with source page that contains td_draft\n            expect(savedRequest.draft).to.equal(true);\n\n            // Case 2 - request without source page that contains td_draft\n            //expect(savedRequest.draft).to.be.undefined;\n\n            // get all requests -> should be 0\n\n            chai.request(server)\n                .get('/' + savedProject._id + '/requests?draft=false')\n                .auth(email, pwd)\n                .end((err, res) => {\n\n                  if (err) { console.error(\"err: \", err ) };\n                  if (log) { console.log(\"res.body: \", res.body) }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  res.body.requests.should.be.a('array');\n\n                  // Case 1 - request with source page that contains td_draft\n                  expect(res.body.requests.length).to.equal(0);\n\n                  // Case 2 - request without source page that contains td_draft\n                  //expect(res.body.requests.length).to.equal(1);\n\n                  done();\n                })\n\n\n          }).catch((err) => {\n            console.error(\"error creating request: \", err)\n          })\n        })\n\n      })\n    })\n\n  })\n\n\n  // mocha test/requestRoute.js  --grep 'add-tag-to-conversation'\n  it('add-tag-to-conversation', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.log(\"err: \", err) };\n            if (log) { console.log(\"res.body: \", res.body) };\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('request_id').not.eql(null);\n            \n            let request_id = res.body.request_id;\n\n            let tags = [\n              { tag: \"tag1\", color: \"#43B1F2\" },\n              { tag: \"tag2\", color: \"#43B1F2\" }\n            ]\n\n            // First Step: add 2 tags on a conversation no tagged at all\n            chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + request_id + '/tag' )\n                .auth(email, pwd)\n                .send(tags)\n                .end((err, res) => {\n\n                  if (err) { console.log(\"err: \", err) };\n                  if (log) { console.log(\"res.body: \", res.body) };\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.tags).to.have.length(2);\n                  expect(res.body.tags[0].tag).to.equal('tag1');\n                  expect(res.body.tags[1].tag).to.equal('tag2');\n              \n                  let tags2 = [\n                    { tag: \"tag2\", color: \"#43B1F2\"},\n                    { tag: \"tag3\", color: \"#43B1F2\"}\n                  ]\n\n                  // Second Step: add more 2 tags of which one already existant in the conversation\n                  chai.request(server)\n                      .put('/' + savedProject._id + '/requests/' + request_id + '/tag')\n                      .auth(email, pwd)\n                      .send(tags2)\n                      .end((err, res) => {\n                        \n                        if (err) { console.log(\"err: \", err) };\n                        if (log) { console.log(\"res.body: \", res.body) };\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.tags).to.have.length(3);\n                        expect(res.body.tags[0].tag).to.equal('tag1');\n                        expect(res.body.tags[1].tag).to.equal('tag2');\n                        expect(res.body.tags[2].tag).to.equal('tag3');\n                        \n                        done();\n                      })\n\n                })\n        \n          });\n      });\n    });\n  });\n\n  // mocha test/requestRoute.js  --grep 'remove-tag-from-conversation'\n  it('remove-tag-from-conversation', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.log(\"err: \", err) };\n            if (log) { console.log(\"res.body: \", res.body) };\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('request_id').not.eql(null);\n            \n            let request_id = res.body.request_id;\n\n            let tags = [\n              { tag: \"tag1\", color: \"#43B1F2\" },\n              { tag: \"tag2\", color: \"#43B1F2\" }\n            ]\n\n            // First Step: add 2 tags on a conversation no tagged at all\n            chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + request_id + '/tag' )\n                .auth(email, pwd)\n                .send(tags)\n                .end((err, res) => {\n\n                  if (err) { console.log(\"err: \", err) };\n                  if (log) { console.log(\"res.body: \", res.body) };\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.tags).to.have.length(2);\n                  expect(res.body.tags[0].tag).to.equal('tag1');\n                  expect(res.body.tags[1].tag).to.equal('tag2');\n              \n                  let tags2 = [\n                    { tag: \"tag2\", color: \"#43B1F2\"},\n                    { tag: \"tag3\", color: \"#43B1F2\"}\n                  ]\n\n                  // Second Step: add more 2 tags of which one already existant in the conversation\n                  chai.request(server)\n                      .put('/' + savedProject._id + '/requests/' + request_id + '/tag')\n                      .auth(email, pwd)\n                      .send(tags2)\n                      .end((err, res) => {\n                        \n                        if (err) { console.log(\"err: \", err) };\n                        if (log) { console.log(\"res.body: \", res.body) };\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n                        expect(res.body.tags).to.have.length(3);\n                        expect(res.body.tags[0].tag).to.equal('tag1');\n                        expect(res.body.tags[1].tag).to.equal('tag2');\n                        expect(res.body.tags[2].tag).to.equal('tag3');\n\n                        let tag_to_delete = res.body.tags[2];\n\n                        chai.request(server)\n                            .delete('/' + savedProject._id + '/requests/' +  request_id + '/tag/' + tag_to_delete._id)\n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                              if (err) { console.log(\"err: \", err) };\n                              if (log) { console.log(\"res.body: \", res.body) };\n                              \n                              res.should.have.status(200);\n                              res.body.should.be.a('object');\n\n                              expect(res.body.tags).to.have.length(2);\n                              expect(res.body.tags[0].tag).to.equal('tag1');\n                              expect(res.body.tags[1].tag).to.equal('tag2');\n\n                              done();\n                            })\n                      })\n\n                })\n        \n          });\n      });\n    });\n  }).timeout(4000);\n\n  it('remove-tag-from-unexistent-conversation', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/requests/')\n          .auth(email, pwd)\n          .set('content-type', 'application/json')\n          .send({ \"first_text\": \"first_text\" })\n          .end(function (err, res) {\n\n            if (err) { console.log(\"err: \", err) };\n            if (log) { console.log(\"res.body: \", res.body) };\n\n            res.should.have.status(200);\n            res.body.should.be.a('object');\n            res.body.should.have.property('request_id').not.eql(null);\n            \n            let request_id = res.body.request_id;\n\n            let tags = [\n              { tag: \"tag1\", color: \"#43B1F2\" },\n              { tag: \"tag2\", color: \"#43B1F2\" }\n            ]\n\n            // First Step: add 2 tags on a conversation no tagged at all\n            chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + request_id + '/tag' )\n                .auth(email, pwd)\n                .send(tags)\n                .end((err, res) => {\n\n                  if (err) { console.log(\"err: \", err) };\n                  if (log) { console.log(\"res.body: \", res.body) };\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.tags).to.have.length(2);\n                  expect(res.body.tags[0].tag).to.equal('tag1');\n                  expect(res.body.tags[1].tag).to.equal('tag2');\n              \n                  let tag_to_delete = res.body.tags[1];\n                  let fake_request_id = \"support-group-6752d23518dbe16860ff2cda-b1f2ecb1c617492fbbc33105b475axxx\"\n\n                  chai.request(server)\n                      .delete('/' + savedProject._id + '/requests/' +  fake_request_id + '/tag/' + tag_to_delete._id)\n                      .auth(email, pwd)\n                      .end((err, res) => {\n\n                        if (err) { console.log(\"err: \", err) };\n                        if (log) { console.log(\"res.body: \", res.body) };\n                        \n                        res.should.have.status(404);\n                        res.body.should.be.a('object');\n\n                        expect(res.body.success).to.equal(false);\n                        expect(res.body.error).to.equal(\"Request not found with id \" + fake_request_id)\n\n                        done();\n                      })\n\n\n                })\n        \n          });\n      });\n    });\n  }).timeout(4000);\n\n\n  describe('/assign', () => {\n\n    // mocha test/requestRoute.js  --grep 'createAndReassign'\n    it('createAndReassign', function (done) {\n      // this.timeout(10000);\n\n      var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"request-create\", savedUser._id).then(function (savedProject) {\n\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/requests/')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({ \"first_text\": \"first_text\" })\n            .end(function (err, res) {\n              \n              if (err) { console.error(\"err: \",  err); }\n              if (log) { console.log(\"res.body\",  res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              // res.body.should.have.property('request_id').eql('request_id');\n              // res.body.should.have.property('requester_id').eql('requester_id');\n              res.body.should.have.property('first_text').eql('first_text');\n              res.body.should.have.property('id_project').eql(savedProject._id.toString());\n              res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n\n              // res.body.should.have.property('messages_count').gt(0);\n\n              res.body.should.have.property('status').eql(200);\n\n              // res.body.should.have.property('agents').eql(savedUser._id);\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              expect(res.body.participants.length).to.equal(1);\n\n              expect(res.body.participantsAgents.length).to.equal(1);\n              expect(res.body.participantsBots).to.have.lengthOf(0);\n              expect(res.body.hasBot).to.equal(false);\n\n              res.body.should.have.property('department').not.eql(null);\n              // res.body.should.have.property('lead').eql(undefined);\n\n              if (log) { console.log(\"res.body.request_id: \" + res.body.request_id); }\n\n              chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + res.body.request_id + \"/departments\")\n                .auth(email, pwd)\n                .set('content-type', 'application/json')\n                .send({})\n                .end(function (err, res2) {\n                  \n                  if (err) { console.error(\"err: \",  err); }\n                  if (log) { console.log(\"res.body\",  res2.body); }\n\n                  res2.should.have.status(200);\n                  res2.body.should.be.a('object');\n                  expect(res.body.participants.length).to.equal(1);\n                  expect(res.body.participantsAgents.length).to.equal(1);\n                  expect(res.body.participantsBots.length).to.equal(0);\n\n\n                  // expect(res.body.snapshot.agents).to.equal(undefined);    \n\n                  res2.body.requester.should.be.a('object');\n                  res2.body.lead.should.be.a('object');\n                  // expect(res.body.requester).to.equal(undefined);                \n                  // expect(res.body.lead).to.equal(undefined);                \n\n                  done();\n                });\n\n            });\n        });\n      });\n    });\n\n\n    // mocha test/requestRoute.js  --grep 'createAndReassignAndNoPopulate'\n    it('createAndReassignAndNoPopulate', function (done) {\n      // this.timeout(10000);\n\n      var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"request-create\", savedUser._id).then(function (savedProject) {\n\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/requests/')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({ \"first_text\": \"first_text\" })\n            .end(function (err, res) {\n              \n              if (err) { console.error(\"err: \",  err); }\n              if (log) { console.log(\"res.body\",  res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              // res.body.should.have.property('request_id').eql('request_id');\n              // res.body.should.have.property('requester_id').eql('requester_id');\n              res.body.should.have.property('first_text').eql('first_text');\n              res.body.should.have.property('id_project').eql(savedProject._id.toString());\n              res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n\n              // res.body.should.have.property('messages_count').gt(0);\n\n              res.body.should.have.property('status').eql(200);\n\n              // res.body.should.have.property('agents').eql(savedUser._id);\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              expect(res.body.participants.length).to.equal(1);\n\n              expect(res.body.participantsAgents.length).to.equal(1);\n              expect(res.body.participantsBots).to.have.lengthOf(0);\n              expect(res.body.hasBot).to.equal(false);\n\n              res.body.should.have.property('department').not.eql(null);\n              // res.body.should.have.property('lead').eql(undefined);\n\n              if (log) { console.log(\"res.body.request_id: \" + res.body.request_id); }\n\n              chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + res.body.request_id + \"/departments\")\n                .auth(email, pwd)\n                .set('content-type', 'application/json')\n                .send({ \"no_populate\": \"true\" })\n                .end(function (err, res2) {\n                  \n                  if (err) { console.error(\"err: \",  err); }\n                  if (log) { console.log(\"res.body\",  res.body); }\n\n                  res2.should.have.status(200);\n                  res2.body.should.be.a('object');\n                  expect(res.body.participants.length).to.equal(1);\n                  expect(res.body.participantsAgents.length).to.equal(1);\n                  expect(res.body.participantsBots.length).to.equal(0);\n\n                  // expect(res.body.snapshot.agents).to.equal(undefined);    \n\n                  // no_populated disabled for route() method in RequestService.js\n                  //res2.body.requester.should.be.a('string');\n                  //res2.body.lead.should.be.a('string');\n                  \n                  // expect(res.body.requester).to.equal(undefined);                \n                  // expect(res.body.lead).to.equal(undefined);                \n\n                  done();\n                });\n\n            });\n        });\n      });\n    });\n\n\n    // mocha test/requestRoute.js  --grep 'createAndAssign2'\n    it('createAndAssign2', function (done) {\n      // this.timeout(10000);\n\n      var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"request-create\", savedUser._id).then(function (savedProject) {\n\n\n          chai.request(server)\n            .post('/' + savedProject._id + '/requests/')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({ \"first_text\": \"first_text\" })\n            .end(function (err, res) {\n              \n              if (err) { console.error(\"err: \",  err); }\n              if (log) { console.log(\"res.body\",  res.body); }\n\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              // res.body.should.have.property('request_id').eql('request_id');\n              // res.body.should.have.property('requester_id').eql('requester_id');\n              res.body.should.have.property('first_text').eql('first_text');\n              res.body.should.have.property('id_project').eql(savedProject._id.toString());\n              res.body.should.have.property('createdBy').eql(savedUser._id.toString());\n\n              // res.body.should.have.property('messages_count').gt(0);\n\n              res.body.should.have.property('status').eql(200);\n\n              // res.body.should.have.property('agents').eql(savedUser._id);\n              //expect(res.body.snapshot.agents.length).to.equal(1);\n              expect(res.body.participants.length).to.equal(1);\n\n              expect(res.body.participantsAgents.length).to.equal(1);\n              expect(res.body.participantsBots).to.have.lengthOf(0);\n              expect(res.body.hasBot).to.equal(false);\n\n              res.body.should.have.property('department').not.eql(null);\n              // res.body.should.have.property('lead').eql(undefined);\n\n              if (log) { console.log(\"res.body.request_id: \" + res.body.request_id); }\n\n              chai.request(server)\n                .put('/' + savedProject._id + '/requests/' + res.body.request_id + \"/assign\")\n                .auth(email, pwd)\n                .set('content-type', 'application/json')\n                .send({})\n                .end(function (err, res2) {\n                  \n                  if (err) { console.error(\"err: \",  err); }\n                  if (log) { console.log(\"res.body\",  res.body); }\n\n                  res2.should.have.status(200);\n                  res2.body.should.be.a('object');\n                  expect(res.body.participants.length).to.equal(1);\n                  expect(res.body.participantsAgents.length).to.equal(1);\n                  expect(res.body.participantsBots.length).to.equal(0);\n\n                  // expect(res.body.snapshot.agents).to.equal(undefined);    \n\n                  res2.body.requester.should.be.a('string');\n                  res2.body.lead.should.be.a('string');\n                  // expect(res.body.requester).to.equal(undefined);                \n                  // expect(res.body.lead).to.equal(undefined);                \n\n                  done();\n                });\n\n            });\n        });\n      });\n    });\n\n\n    // mocha test/requestRoute.js  --grep 'removeParticipant'\n    it('removeParticipant', function (done) {\n      // this.timeout(10000);\n\n      var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n\n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.createAndReturnProjectAndProjectUser(\"request-removeParticipant\", savedUser._id).then(function (savedProjectAndPU) {\n          var savedProject = savedProjectAndPU.project;\n\n          leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n            winston.info(\"savedProjectAndPU.project_user._id:\" + savedProjectAndPU.project_user._id);\n\n            var now = Date.now();\n\n            //  projectService.create(\"request-removeParticipant\", savedUser._id).then(function(savedProject) {\n            // requestService.removeParticipantByRequestId(savedRequest.request_id, savedProject._id, userid).then(function(savedRequestParticipant) {\n            var request = {\n              request_id: \"request_id1-removeParticipant-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n              id_project: savedProject._id, first_text: \"first_text\",\n              lead: createdLead, requester: savedProjectAndPU.project_user\n            };\n\n            requestService.create(request).then(function (savedRequest) {\n              winston.info(\"savedRequest\", savedRequest.toObject());\n              expect(savedRequest.request_id).to.equal(\"request_id1-removeParticipant-\" + now);\n\n              chai.request(server)\n                .delete('/' + savedProject._id + '/requests/' + 'request_id1-removeParticipant-' + now + \"/participants/\" + savedUser._id)\n                .auth(email, pwd)\n                .set('content-type', 'application/json')\n                .send({ \"text\": \"first_text\" })\n                .end(function (err, res) {\n                  \n                  if (err) { console.error(\"err: \",  err); }\n                  if (log) { console.log(\"res.body\",  res.body); }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n\n                  // expect(res.body.snapshot.agents.length).to.equal(1);\n                  // res.body.should.have.property('request_id').eql('request_id');\n                  // res.body.should.have.property('requester_id').eql('requester_id');\n                  res.body.should.have.property('first_text').eql('first_text');\n                  res.body.should.have.property('id_project').eql(savedProject._id.toString());\n                  // res.body.should.have.property('createdBy').eql(savedUser._id.toString()); ?? expected '607ef36a2d060d79dc83ac9f' to deeply equal '607ef3692d060d79dc83ac9d'\n\n                  // res.body.should.have.property('messages_count').gt(0);\n\n                  res.body.should.have.property('status').eql(100);\n\n                  // res.body.should.have.property('agents').eql(savedUser._id);\n                  // expect(res.body.snapshot.agents.length).to.equal(1);\n                  expect(res.body.participants.length).to.equal(0);\n\n                  expect(res.body.participantsAgents.length).to.equal(0);\n                  expect(res.body.participantsBots).to.have.lengthOf(0);\n                  expect(res.body.hasBot).to.equal(false);\n                  winston.info(\"res.body.attributes.abandoned_by_project_users\", res.body.attributes.abandoned_by_project_users);\n                  expect(res.body.attributes.abandoned_by_project_users[savedProjectAndPU.project_user._id]).to.not.equal(undefined);\n                  //expect(res.body.snapshot.agents).to.equal(undefined);\n\n                  res.body.should.have.property('department').not.eql(null);\n                  // res.body.should.have.property('lead').eql(undefined);\n\n\n                  done();\n                });\n            });\n          });\n        });\n      });\n    });\n\n\n    // it('assign', (done) => {\n\n\n    // //   this.timeout();\n\n    //    var email = \"test-signup-\" + Date.now() + \"@email.com\";\n    //    var pwd = \"pwd\";\n\n    //     userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n    //         projectService.create(\"test-join-member\", savedUser._id).then(function(savedProject) {\n    //             requestService.createWithId(\"join-member\", \"requester_id1\", savedProject._id, \"first_text\").then(function(savedRequest) {\n\n    //                 var webhookContent =     { \"assignee\": 'assignee-member'}\n\n\n    //                 chai.request(server)\n    //                     .post('/'+ savedProject._id + '/requests/' + savedRequest.request_id + '/assign')\n    //                     .auth(email, pwd)\n    //                     .send(webhookContent)\n    //                     .end((err, res) => {\n    //                         //console.log(\"res\",  res);\n    //                         console.log(\"res.body\",  res.body);\n    //                         res.should.have.status(200);\n    //                         res.body.should.be.a('object');\n    //                         // res.body.should.have.property('status').eql(200);\n\n\n    //                         // res.body.should.have.property('participants').to.have.lengthOf(2);\n    //                         // res.body.should.have.property('participants').contains(\"agentid1\");\n    //                         // res.body.should.have.property('participants').contains(savedUser._id);\n\n    //                         done();\n    //                     });\n\n\n    //             });\n    //             });\n    //             });\n    // }).timeout(20000);\n\n    /*\n    it('requestParameterFromChatbot', function (done) {\n      // this.timeout(10000);\n  \n      var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n      var pwd = \"pwd\";\n  \n      userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n        projectService.create(\"request-create\", savedUser._id, { email: { template: { assignedRequest: \"123\" } } }).then(function (savedProject) {\n  \n          chai.request(server)\n            .post('/' + savedProject._id + '/requests/')\n            .auth(email, pwd)\n            .set('content-type', 'application/json')\n            .send({ \"first_text\": \"first_text\" })\n            .end(function (err, res) {\n              //console.log(\"res\",  res);\n              //console.log(\"res.body\",  res.body);\n              res.should.have.status(200);\n              res.body.should.be.a('object');\n  \n              console.log(\"body: \", res.body);\n              let request_id = res.body.request_id;\n\n              console.log(\"request_id: \", request_id);\n\n              chai.request(server)\n                  .get('/' + savedProject._id + '/requests/' + request_id + \"/chatbot/parameters\")\n                  .auth(email, pwd)\n                  .end((err, res) => {\n\n                    console.log(\"err: \", err);\n                    console.log(\"res.body: \", res.body);\n                    done();\n                  })\n  \n            });\n        });\n      });\n    });\n\n    */\n\n  });\n\n});\n\n\n"
  },
  {
    "path": "test/requestService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nlet chai = require('chai');\nchai.config.includeStack = true;\n\nvar expect = chai.expect;\nvar assert = chai.assert;\nlet should = chai.should();\nvar config = require('../config/database');\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\nvar RoleConstants = require('../models/roleConstants');\n\nlet log = false;\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\nrequire('../services/mongoose-cache-fn')(mongoose);\n\nvar requestService = require('../services/requestService');\nvar messageService = require('../services/messageService');\nvar projectService = require('../services/projectService');\nvar projectUserService = require('../services/projectUserService');\nvar departmentService = require('../services/departmentService');\nvar leadService = require('../services/leadService');\nvar userService = require('../services/userService');\n\nvar Request = require(\"../models/request\");\nvar Group = require(\"../models/group\");\nvar Project_user = require(\"../models/project_user\");\n// var Tag = require('../models/tag');\nvar requestEvent = require('../event/requestEvent');\n\nvar { QuoteManager } = require('../services/QuoteManager');\nconst projectMock = require('./mock/projectMock');\n\n// CONNECT REDIS - CHECK IT\nconst { TdCache } = require('../utils/TdCache');\nlet tdCache = new TdCache({\n  host: process.env.CACHE_REDIS_HOST || '127.0.0.1',\n  port: process.env.CACHE_REDIS_PORT || '6379',\n  password: process.env.CACHE_REDIS_PASSWORD || undefined\n});\n\ntdCache.connect();\n\n// var redis = require('redis')\n// var redis_client;\n\n// connectRedis();\n\n// function connectRedis() {\n//   console.log(\">>> connectRedis for test\")\n//   redis_client = redis.createClient({\n//     host: \"127.0.0.1\",\n//     port: 6379,\n//   });\n\n//   redis_client.on('error', err => {\n//     winston.info('(wab) Connect Redis Error ' + err);\n//   })\n\n//   redis_client.on('ready', () => {\n//     winston.info(\"(wab) Redis ready!\")\n//   })\n\n// }\n\ndescribe('RequestService', function () {\n\n  // var userid = \"5badfe5d553d1844ad654072\";\n\n  // mocha test/requestService.js  --grep 'createObjSimple'\n  it('createObjSimpleQuote', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n\n    let qm = new QuoteManager({ tdCache: tdCache });\n    qm.start();\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser.id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_id-createObjSimple-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n          // attributes: { sourcePage: \"https://widget-pre.tiledesk.com/v2/index.html?tiledesk_projectid=5ce3d1ceb25ad30017279999&td_draft=true\" } // for quote test\n\n          requestService.create(request).then(async function (savedRequest) {\n            winston.verbose(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id-createObjSimple-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            expect(savedRequest.department).to.not.equal(null);\n            expect(savedRequest.ticket_id).to.equal(1);\n            expect(savedRequest.status).to.equal(200);\n            expect(savedRequest.participants).to.have.lengthOf(1);\n            expect(savedRequest.participants).to.contains(userid);\n            expect(savedRequest.participantsAgents).to.contains(userid);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n\n            expect(savedRequest.participants[0].toString()).to.equal(userid);\n            expect(savedRequest.participantsAgents[0].toString()).to.equal(userid);\n            expect(savedRequest.assigned_at).to.not.equal(null);\n\n            // expect(savedRequest.snapshot.department.name).to.not.equal(null);\n            // expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            // expect(savedRequest.snapshot.lead.fullname).to.equal(\"leadfullname\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            // expect(savedRequest.snapshot.requester.isAuthenticated).to.equal(true);\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n            \n            //expect(savedRequest.attributes.sourcePage).to.equal(\"https://widget-pre.tiledesk.com/v2/index.html?tiledesk_projectid=5ce3d1ceb25ad30017279999&td_draft=true\")\n\n\n            setTimeout(async () => {\n              let obj = { createdAt: new Date() }\n  \n              let quotes = await qm.getAllQuotes(savedProject, obj);\n              if (log) { console.log(\"quotes: \", quotes); }\n              // quotes.requests.quote.should.be.a('string');\n              // expect(quotes.requests.quote).to.equal('1');\n              \n              done();\n            }, 200);\n\n\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            // done();\n          });\n        });\n      });\n    });\n  }).timeout(10000);\n\n  it('createObjSimple', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n\n    let qm = new QuoteManager({ tdCache: tdCache });\n    qm.start();\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser.id).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_id-createObjSimple-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.verbose(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id-createObjSimple-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            expect(savedRequest.department).to.not.equal(null);\n            expect(savedRequest.ticket_id).to.equal(1);\n            expect(savedRequest.status).to.equal(200);\n            expect(savedRequest.participants).to.have.lengthOf(1);\n            expect(savedRequest.participants).to.contains(userid);\n            expect(savedRequest.participantsAgents).to.contains(userid);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            expect(savedRequest.participants[0].toString()).to.equal(userid);\n            expect(savedRequest.participantsAgents[0].toString()).to.equal(userid);\n            expect(savedRequest.assigned_at).to.not.equal(null);\n            if (log) { console.log(\"savedRequest.participants1\"); }\n\n            // expect(savedRequest.snapshot.department.name).to.not.equal(null);\n            // expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            // expect(savedRequest.snapshot.lead.fullname).to.equal(\"leadfullname\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            // expect(savedRequest.snapshot.requester.isAuthenticated).to.equal(true);\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            if (log) { console.log(\"savedRequest.participants2\"); }\n\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n            // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n            if (log) { console.log(\"savedRequest.participants3\"); }\n            // aiuto\n            // expect(savedRequest.department).to.equal(\"requester_id1\");\n\n            requestService.create(request).then(function (savedRequest) {\n              // assert.isNotOk('No duplicate check index');\n              if (log) { console.log(\"no index check ???\"); }\n              // done();\n            }).catch(function (err) {\n              if (log) { console.log(\"ok duplicate check index \", err); }\n              done();\n            });\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            // done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n  // mocha test/requestService.js  --grep 'createObjSimpleUpdateLeadUpdateSnapshot'\n\n  it('createObjSimpleUpdateLeadUpdateSnapshot', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_idcreateObjSimpleUpdateLeadUpdateSnapshot-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.info(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_idcreateObjSimpleUpdateLeadUpdateSnapshot-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            expect(savedRequest.department).to.not.equal(null);\n            expect(savedRequest.status).to.equal(200);\n            expect(savedRequest.participants).to.have.lengthOf(1);\n            expect(savedRequest.participants).to.contains(userid);\n            expect(savedRequest.participantsAgents).to.contains(userid);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            expect(savedRequest.participants[0].toString()).to.equal(userid);\n            expect(savedRequest.participantsAgents[0].toString()).to.equal(userid);\n            expect(savedRequest.assigned_at).to.not.equal(null);\n\n            // expect(savedRequest.snapshot.department.name).to.not.equal(null);\n            // expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            // expect(savedRequest.snapshot.lead.fullname).to.equal(\"leadfullname\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            // expect(savedRequest.snapshot.requester.isAuthenticated).to.equal(true);\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n            // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n\n            leadService.updateWitId(createdLead.lead_id, \"fullname2\", \"email2@email2.com\", savedProject._id).then(function (updatedLead) {\n\n              expect(updatedLead.fullname).to.equal(\"fullname2\");\n              expect(updatedLead.email).to.equal(\"email2@email2.com\");\n              expect(updatedLead.id_project).to.equal(savedProject._id.toString());\n              expect(updatedLead.lead_id).to.not.equal(createdLead.id);\n              if (log) { console.log(\"updatedLead\", updatedLead); }\n\n              requestEvent.on('request.update.snapshot.lead', function (data) {\n\n                Request.findById(savedRequest._id, function (err, request) {\n\n                  if (err) { console.error(\"err\", err); }\n                  \n                  expect(request.request_id).to.equal(\"request_idcreateObjSimpleUpdateLeadUpdateSnapshot-\" + now);\n                  //expect(request.snapshot.lead.fullname).to.equal(\"fullname2\");\n                  done();\n                });\n\n              });\n\n\n\n            });\n\n\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n  // mocha test/requestService.js  --grep 'createObjParticipantsAgent'\n\n  it('createObjParticipantsAgent', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_id-createObjParticipantsAgent-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user,\n            participants: [userid.toString()]\n          };\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.debug(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id-createObjParticipantsAgent-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequest.department).to.equal(undefined);\n            expect(savedRequest.status).to.equal(200);\n            expect(savedRequest.participants).to.have.lengthOf(1);\n            expect(savedRequest.participants).to.contains(userid);\n            expect(savedRequest.participantsAgents).to.contains(userid);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            expect(savedRequest.participants[0].toString()).to.equal(userid);\n            expect(savedRequest.participantsAgents[0].toString()).to.equal(userid);\n            expect(savedRequest.assigned_at).to.not.equal(null);\n\n            // expect(savedRequest.snapshot.department).to.equal(undefined);\n            // expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            // expect(savedRequest.snapshot.lead.fullname).to.equal(\"leadfullname\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n            // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n            // aiuto\n            // expect(savedRequest.department).to.equal(\"requester_id1\");\n            done();\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n\n  // mocha test/requestService.js  --grep 'createObjTemp'\n\n  it('createObjTemp', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_id-createObjTemp-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            lead: createdLead, requester: savedProjectAndPU.project_user,\n            status: 50\n          };\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.debug(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id-createObjTemp-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequest.department).to.not.equal(undefined);\n            expect(savedRequest.status).to.equal(50);\n            expect(savedRequest.participants).to.have.lengthOf(0);\n            expect(savedRequest.participantsAgents).to.have.lengthOf(0);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            expect(savedRequest.assigned_at).to.equal(undefined);\n\n            // expect(savedRequest.snapshot.department.name).to.not.equal(null);\n            // expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            // expect(savedRequest.snapshot.lead.fullname).to.equal(\"leadfullname\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n            // expect(savedRequest.snapshot.requester.role).to.equal(\"owner\");\n\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n            // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n            // aiuto\n            // expect(savedRequest.department).to.equal(\"requester_id1\");\n            done();\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  it('createWithIdAndCreateNewLead', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          requestService.createWithIdAndRequester(\"request_id-createWithIdAndCreateNewLead-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n            winston.debug(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id-createWithIdAndCreateNewLead-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequest.status).to.equal(200);\n            expect(savedRequest.participants).to.have.lengthOf(1);\n            expect(savedRequest.participants).to.contains(userid);\n            expect(savedRequest.participantsAgents).to.contains(userid);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            expect(savedRequest.participants[0].toString()).to.equal(userid);\n            expect(savedRequest.participantsAgents[0].toString()).to.equal(userid);\n            expect(savedRequest.assigned_at).to.not.equal(null);\n\n            // expect(savedRequest.snapshot.department.name).to.not.equal(null);\n            // expect(savedRequest.snapshot.agents).to.not.equal(null);\n            // expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n            expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n            // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n            // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n            // aiuto\n            // expect(savedRequest.department).to.equal(\"requester_id1\");\n            done();\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n  it('createWithIdAndCreateNewLeadAndCheckRequestEvent', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n\n          requestEvent.on('request.create', function (savedRequest) {\n\n            if (savedRequest.request_id === \"createWithIdAndCreateNewLeadAndCheckRequestEvent-\" + now) {\n\n\n              if (log) { console.log(\"savedRequest\", savedRequest.toJSON()); }\n\n              winston.debug(\"resolve\", savedRequest.toObject());\n              expect(savedRequest.request_id).to.equal(\"createWithIdAndCreateNewLeadAndCheckRequestEvent-\" + now);\n              expect(savedRequest.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n              expect(savedRequest.first_text).to.equal(\"first_text\");\n              //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n              expect(savedRequest.status).to.equal(200);\n              expect(savedRequest.participants).to.have.lengthOf(1);\n              expect(savedRequest.participants).to.contains(userid);\n              expect(savedRequest.participantsAgents).to.contains(userid);\n              expect(savedRequest.participantsBots).to.have.lengthOf(0);\n              expect(savedRequest.hasBot).to.equal(false);\n              if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n              expect(savedRequest.participants[0].toString()).to.equal(userid);\n\n              expect(savedRequest.createdBy).to.equal(savedProjectAndPU.project_user._id.toString());\n\n              expect(savedRequest.participatingAgents.length).to.equal(1);\n              expect(savedRequest.participatingBots.length).to.equal(0);\n              //expect(savedRequest.snapshot.availableAgentsCount).to.equal(1);\n\n              // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n              // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n              expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n              // aiuto\n              // expect(savedRequest.department).to.equal(\"requester_id1\");\n              done();\n            }\n\n          });\n          requestService.createWithIdAndRequester(\"createWithIdAndCreateNewLeadAndCheckRequestEvent-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n          }).catch(function (err) {\n            if (log) { console.log(\"test reject\", err); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n\n  });\n\n\n\n\n  // it('createWithIdLead', function (done) {\n  //   // this.timeout(10000);\n\n  //    projectService.create(\"createWithId\", userid).then(function(savedProject) {\n  //     leadService.createIfNotExists(\"leadfullname\", \"email@email.com\",  savedProject._id).then(function(lead) {\n  //     // createWithId(request_id, requester_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status) {\n  //      requestService.createWithId(\"request_id1\", lead._id,  savedProject._id, \"first_text\").then(function(savedRequest) {\n  //        leadService.findByEmail(\"email@email.com\", savedProject._id).then(function(lead) {\n  //         winston.debug(\"resolve\", savedRequest);\n  //         expect(savedRequest.request_id).to.equal(\"request_id1\");\n  //         expect(savedRequest.requester_id).to.equal(lead._id.toString());\n  //         expect(savedRequest.first_text).to.equal(\"first_text\");\n  //         expect(savedRequest.agents).to.have.lengthOf(1);\n  //         expect(savedRequest.status).to.equal(200);\n  //         expect(savedRequest.participants).to.contains(userid);\n  //         expect(savedRequest.createdBy).to.equal(lead._id.toString());\n\n  //         // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n  //         // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n  //         expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n  //         // aiuto\n  //         // expect(savedRequest.department).to.equal(\"requester_id1\");\n  //         done();\n  //       }).catch(function(err) {\n  //           console.log(\"test reject\");\n  //           assert.isNotOk(err,'Promise error');\n  //           done();\n  //       });\n  //     });\n  //     });\n  // });\n\n  // });\n\n\n\n\n\n\n  it('createWithIdAndCreatedBy', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithIdAndCreatedBy\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {\n        requestService.createWithIdAndRequester(\"request_id-createWithIdAndCreatedBy-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\", null, null, null, null, null, \"user1\").then(function (savedRequest) {\n          \n          expect(savedRequest.request_id).to.equal(\"request_id-createWithIdAndCreatedBy-\" + now);\n          expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n          expect(savedRequest.first_text).to.equal(\"first_text\");\n          //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n          expect(savedRequest.status).to.equal(200);\n          expect(savedRequest.participants).to.contains(userid);\n          expect(savedRequest.participantsAgents).to.contains(userid);\n          expect(savedRequest.participantsBots).to.have.lengthOf(0);\n          expect(savedRequest.hasBot).to.equal(false);\n          expect(savedRequest.createdBy).to.equal(\"user1\");\n\n          // console.log(\"savedProject._id\", savedProject._id, typeof savedProject._id);\n          // console.log(\"savedRequest.id_project\", savedRequest.id_project, typeof savedRequest.id_project);\n\n          expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n          // aiuto\n          // expect(savedRequest.department).to.equal(\"requester_id1\");\n          done();\n        })\n          .catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n      });\n    });\n  });\n\n\n\n  // mocha test/requestService.js  --grep 'createWithWrongDepartmentId'\n\n  it('createWithWrongDepartmentId', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithWrongDepartmentId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        // createWithIdAndRequester(request_id, project_user_id, lead_id, id_project, first_text, departmentid, sourcePage, language, userAgent, status, createdBy, attributes) {\n        requestService.createWithIdAndRequester(\"request_idcreateWithWrongDepartmentId-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\", \"5ebd890b3f2702001915c89e\", null, null, null, null, \"user1\").then(function (savedRequest) {\n\n        })\n          .catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            done();\n          });\n      });\n    });\n  });\n\n\n\n  it('createWithIdWithPooledDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithIdWithPooledDepartment\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        departmentService.create(\"PooledDepartment-for-createWithIdWith-createWithIdWithPooledDepartment\", savedProject._id, 'pooled', userid).then(function (createdDepartment) {\n          var now = Date.now();\n          requestService.createWithIdAndRequester(\"request_id-createWithIdWithPooledDepartment-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\", createdDepartment._id).then(function (savedRequest) {\n            winston.debug(\"resolve savedRequest\");\n            expect(savedRequest.request_id).to.equal(\"request_id-createWithIdWithPooledDepartment-\" + now);\n            expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequest.first_text).to.equal(\"first_text\");\n            //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequest.status).to.equal(100);\n            expect(savedRequest.participants).to.have.lengthOf(0);\n            expect(savedRequest.participantsAgents).to.have.lengthOf(0);\n            expect(savedRequest.participantsBots).to.have.lengthOf(0);\n            expect(savedRequest.hasBot).to.equal(false);\n            expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n            expect(savedRequest.department.toString()).to.equal(createdDepartment._id.toString());\n            done();\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n  // mocha test/requestService.js  --grep 'updateWaitingTimeRequest'\n\n  it('updateWaitingTimeRequest', function (done) {\n    this.timeout(1000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      var messageSender = \"5badfe5d553d1844ad654072\";\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id-waitingTimeRequest-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          setTimeout(function () {\n            Promise.all([\n              messageService.create(messageSender, \"test sender\", savedRequest.request_id, \"hello1\",\n                savedProject._id, messageSender),\n              messageService.create(messageSender, \"test sender\", savedRequest.request_id, \"hello2\",\n                savedProject._id, messageSender)]).then(function (all) {\n                  requestService.updateWaitingTimeByRequestId(savedRequest.request_id, savedProject._id, true).then(function (upRequest) {\n                    var maxWaitingTime = Date.now() - upRequest.createdAt;\n                    if (log) { console.log(\"resolve closedRequest\", upRequest.toObject(), maxWaitingTime); }\n\n                    expect(upRequest.status).to.equal(200);\n                    // console.log(\"1\")\n                    // expect(upRequest.status).to.equal(300);\n                    expect(upRequest.waiting_time).to.not.equal(null);\n                    // console.log(\"2\")\n                    expect(upRequest.waiting_time).to.gte(300);\n                    // console.log(\"3\")\n                    expect(upRequest.waiting_time).to.lte(maxWaitingTime);\n                    // console.log(\"4\")\n                    expect(upRequest.first_response_at).to.not.equal(null);\n                    // console.log(\"5\")\n                    done();\n                  }).catch(function (err) {\n                    winston.error(\"test reject\", err);\n                    assert.isNotOk(err, 'Promise error');\n                    done();\n                  });\n                });\n          }, 300);\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n  // mocha test/requestService.js  --grep 'closeRequest'\n\n\n  it('closeRequest', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id-closeRequest-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          Promise.all([\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello1\",\n              savedProject._id, \"5badfe5d553d1844ad654072\"),\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello2\",\n              savedProject._id, \"5badfe5d553d1844ad654072\")]).then(function (all) {\n                // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by)\n                requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id, false, true, \"user1\").then(function (closedRequest) {\n                  winston.debug(\"resolve closedRequest\", closedRequest.toObject());\n                  expect(closedRequest.status).to.equal(1000);\n                  expect(closedRequest.closed_at).to.not.equal(null);\n                  expect(closedRequest.transcript).to.contains(\"hello1\");\n                  expect(closedRequest.transcript).to.contains(\"hello2\");\n                  //expect(closedRequest.snapshot.agents).to.equal(undefined);\n                  expect(closedRequest.closed_by).to.equal(\"user1\");\n\n                  done();\n                }).catch(function (err) {\n                  winston.error(\"test reject\", err);\n                  assert.isNotOk(err, 'Promise error');\n                  done();\n                });\n              });\n        });\n      });\n    });\n  });\n\n\n\n\n  // mocha test/requestService.js  --grep 'closeRequestForce'\n\n\n  it('closeRequestForce', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id-closeRequest-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          Promise.all([\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello1\",\n              savedProject._id, \"5badfe5d553d1844ad654072\"),\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello2\",\n              savedProject._id, \"5badfe5d553d1844ad654072\")]).then(function (all) {\n                // closeRequestByRequestId(request_id, id_project, skipStatsUpdate, notify, closed_by, force)\n                requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id, false, true, \"user1\").then(function (closedRequest) {\n                  winston.debug(\"resolve closedRequest\", closedRequest.toObject());\n                  expect(closedRequest.status).to.equal(1000);\n                  expect(closedRequest.closed_at).to.not.equal(null);\n                  expect(closedRequest.transcript).to.contains(\"hello1\");\n                  expect(closedRequest.transcript).to.contains(\"hello2\");\n                  //expect(closedRequest.snapshot.agents).to.equal(undefined);\n                  expect(closedRequest.closed_by).to.equal(\"user1\");\n\n                  requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id, false, true, \"user1\", true).then(function (closedRequest) {\n                    expect(closedRequest.status).to.equal(1000);\n                    done();\n                  });\n\n                }).catch(function (err) {\n                  winston.error(\"test reject\", err);\n                  assert.isNotOk(err, 'Promise error');\n                  done();\n                });\n              });\n        });\n      });\n    });\n  });\n\n\n\n\n\n  it('closeRequestAndSendTranscript', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid, { email: { autoSendTranscriptToRequester: true } }).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"andrea.leo@frontiere21.it\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          requestService.createWithIdAndRequester(\"request_id-closeRequestAndSendTranscript-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n            Promise.all([\n              messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello1\",\n                savedProject._id, \"5badfe5d553d1844ad654072\"),\n              messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello2\",\n                savedProject._id, \"5badfe5d553d1844ad654072\")]).then(function (all) {\n                  requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id).then(function (closedRequest) {\n                    winston.debug(\"resolve closedRequest\", closedRequest.toObject());\n                    expect(closedRequest.status).to.equal(1000);\n                    expect(closedRequest.closed_at).to.not.equal(null);\n                    expect(closedRequest.transcript).to.contains(\"hello1\");\n                    expect(closedRequest.transcript).to.contains(\"hello2\");\n                    //ot.agents).to.equal(undefined);\n\n                    done();\n                  }).catch(function (err) {\n                    winston.error(\"test reject\", err);\n                    assert.isNotOk(err, 'Promise error');\n                    done();\n                  });\n                });\n          });\n        });\n      });\n    });\n  });\n\n\n\n  it('reopenRequest', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n\n        requestService.createWithIdAndRequester(\"request_id-reopenRequest-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n          requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id).then(function (closedRequest) {\n            requestService.reopenRequestByRequestId(savedRequest.request_id, savedProject._id).then(function (reopenedRequest) {\n\n              winston.info(\"resolve reopenedRequest\", reopenedRequest.toObject());\n\n              //check closedRequest\n              expect(closedRequest.status).to.equal(1000);\n              expect(closedRequest.closed_at).to.not.equal(null);\n              expect(closedRequest.participants).to.have.lengthOf(1);\n\n              //check reopenedRequest\n              expect(reopenedRequest.status).to.equal(200);\n              expect(reopenedRequest.closed_at).to.not.equal(null);\n              expect(reopenedRequest.participants).to.have.lengthOf(1);\n              //expect(reopenedRequest.snapshot.agents).to.equal(undefined);\n\n\n              done();\n            }).catch(function (err) {\n              winston.error(\"test reject\", err);\n              assert.isNotOk(err, 'Promise error');\n              done();\n            });\n          });\n        });\n      });\n    });\n  });\n\n  // mocha test/requestService.js  --grep 'addparticipant'\n\n  it('addparticipant', function (done) {\n\n    var email = \"test-request-addparticipant-\" + Date.now() + \"@email.com\";\n    var email2 = \"test-request-addparticipant2-\" + Date.now() + \"@email.com\";\n\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"addparticipant-project\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        userService.signup(email2, pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n\n          // console.log(\"savedUser2\",savedUser2);\n\n          var newProject_user = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: savedProject._id.toString(),\n            id_user: savedUser2._id.toString(),\n            role: RoleConstants.AGENT,\n            roleType : RoleConstants.TYPE_AGENTS,   \n            user_available: false,\n            createdBy: userid,\n            updatedBy: userid\n          });\n\n          return newProject_user.save(function (err, savedProject_user) {\n\n            if (err) { console.log(\"err\", err); }\n\n            var now = Date.now();\n            requestService.createWithIdAndRequester(\"request_id-addparticipant-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n              //  inserisci id valido\n              //  var member = 'agent1';\n              var member = savedUser2._id.toString();\n              // console.log(\"member\",member)\n              //  addParticipantByRequestId(request_id, id_project, member) {\n              requestService.addParticipantByRequestId(savedRequest.request_id, savedProject._id, member).then(function (savedRequestParticipant) {\n                winston.info(\"resolve addParticipantByRequestId\", savedRequestParticipant.toObject());\n                expect(savedRequestParticipant.request_id).to.equal(\"request_id-addparticipant-\" + now);\n\n                winston.info(\"savedProjectAndPU.project_user._id.toString():\" + savedProjectAndPU.project_user._id.toString());\n                expect(savedRequestParticipant.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n\n                expect(savedRequestParticipant.first_text).to.equal(\"first_text\");\n                // expect(savedRequestParticipant.snapshot.agents).to.have.lengthOf(2);\n                expect(savedRequestParticipant.status).to.equal(200);\n\n                expect(savedRequestParticipant.participants).to.have.lengthOf(2);\n                expect(savedRequestParticipant.participants).to.contains(userid);\n                expect(savedRequestParticipant.participants).to.contains(member);\n\n                expect(savedRequestParticipant.participantsAgents).to.have.lengthOf(2);\n                expect(savedRequestParticipant.participantsAgents).to.contains(userid);\n                expect(savedRequestParticipant.participantsAgents).to.contains(member);\n\n                expect(savedRequestParticipant.participatingAgents).to.have.lengthOf(2);\n                expect(savedRequestParticipant.participatingBots).to.have.lengthOf(0);\n                expect(savedRequestParticipant.hasBot).to.equal(false);\n                expect(savedRequestParticipant.id_project).to.equal(savedProject._id.toString());\n\n                //expect(savedRequestParticipant.snapshot.agents).to.equal(undefined);\n\n                done();\n              }).catch(function (err) {\n                if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n                assert.isNotOk(err, 'Promise error');\n                done();\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n  it('setParticipantsByRequestId', function (done) {\n\n    var email = \"test-request-setParticipantsByRequestId-\" + Date.now() + \"@email.com\";\n    var email2 = \"test-request-setParticipantsByRequestId2-\" + Date.now() + \"@email.com\";\n\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"setParticipantsByRequestId-project\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        userService.signup(email2, pwd, \"Test Firstname2\", \"Test lastname2\").then(function (savedUser2) {\n\n          // console.log(\"savedUser2\",savedUser2);\n\n          var newProject_user = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: savedProject._id.toString(),\n            id_user: savedUser2._id.toString(),\n            role: RoleConstants.AGENT,\n            roleType : RoleConstants.TYPE_AGENTS,  \n            user_available: false,\n            createdBy: userid,\n            updatedBy: userid\n          });\n\n          return newProject_user.save(function (err, savedProject_user) {\n\n            if (err) { console.log(\"err\", err) }\n            var now = Date.now();\n\n            requestService.createWithIdAndRequester(\"request_id1-setParticipantsByRequestId-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n              expect(savedRequest.participants).to.contains(userid);\n              expect(savedRequest.participantsAgents).to.contains(userid);\n\n              //  inserisci id valido\n              //  var member = 'agent1';\n              var member = savedUser2._id.toString();\n              // console.log(\"member\",member)\n              // setParticipantsByRequestId(request_id, id_project, newparticipants) {\n              requestService.setParticipantsByRequestId(savedRequest.request_id, savedProject._id, [member]).then(function (savedRequestParticipant) {\n                winston.info(\"resolve setParticipantsByRequestId\", savedRequestParticipant.toObject());\n                expect(savedRequestParticipant.request_id).to.equal(\"request_id1-setParticipantsByRequestId-\" + now);\n\n                winston.info(\"savedProjectAndPU.project_user._id.toString():\" + savedProjectAndPU.project_user._id.toString());\n                expect(savedRequestParticipant.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n\n                expect(savedRequestParticipant.first_text).to.equal(\"first_text\");\n                // expect(savedRequestParticipant.snapshot.agents).to.have.lengthOf(2);\n                expect(savedRequestParticipant.status).to.equal(200);\n\n                expect(savedRequestParticipant.participants).to.have.lengthOf(1);\n                expect(savedRequestParticipant.participants).to.contains(member);\n\n                expect(savedRequestParticipant.participantsAgents).to.have.lengthOf(1);\n                expect(savedRequestParticipant.participantsAgents).to.contains(member);\n\n                expect(savedRequestParticipant.participatingAgents).to.have.lengthOf(1);\n                expect(savedRequestParticipant.participatingAgents[0]._id.toString()).to.equal(member);\n                expect(savedRequestParticipant.participatingBots).to.have.lengthOf(0);\n                expect(savedRequestParticipant.hasBot).to.equal(false);\n                expect(savedRequestParticipant.id_project).to.equal(savedProject._id.toString());\n\n                done();\n              }).catch(function (err) {\n                if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n                assert.isNotOk(err, 'Promise error');\n                done();\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n\n  // mocha test/requestService.js  --grep 'removeparticipant'\n\n  it('removeparticipant', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"removeparticipant-project\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id-removeparticipant-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          requestService.removeParticipantByRequestId(savedRequest.request_id, savedProject._id, userid).then(function (savedRequestParticipant) {\n            winston.info(\"resolve\", savedRequestParticipant.toObject());\n\n            //savedRequest is assigned -> 200\n            expect(savedRequest.status).to.equal(200);\n\n            //savedRequestParticipant is UNASSIGNED -> 100\n            expect(savedRequestParticipant.request_id).to.equal(\"request_id-removeparticipant-\" + now);\n            // expect(savedRequestParticipant.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequestParticipant.first_text).to.equal(\"first_text\");\n            // expect(savedRequestParticipant.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequestParticipant.status).to.equal(100);\n            expect(savedRequestParticipant.participants).to.have.lengthOf(0);\n            expect(savedRequestParticipant.participantsAgents).to.have.lengthOf(0);\n            expect(savedRequestParticipant.participantsBots).to.have.lengthOf(0);\n\n            expect(savedRequestParticipant.participatingAgents).to.have.lengthOf(0);\n            expect(savedRequestParticipant.participatingBots).to.have.lengthOf(0);\n\n            expect(savedRequestParticipant.id_project).to.equal(savedProject._id.toString());\n            expect(savedRequestParticipant.attributes.abandoned_by_project_users[savedProjectAndPU.project_user._id]).to.not.equal(undefined);\n\n            done();\n          }).catch(function (err) {\n            console.log(\"test reject\");\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n  // mocha test/requestService.js  --grep 'routeDepartmentSameAgentSameDepartmentSkipUpdate'\n  it('routeDepartmentSameAgentSameDepartmentSkipUpdate', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-route-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"routeDepartmentSameAgentSameDepartmentSkipUpdate\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"routeDepartmentSameAgentSameDepartmentSkipUpdate-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          winston.debug(\"resolve savedRequest\");\n          expect(savedRequest.request_id).to.equal(\"routeDepartmentSameAgentSameDepartmentSkipUpdate-\" + now);\n          expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n          expect(savedRequest.first_text).to.equal(\"first_text\");\n          //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n          expect(savedRequest.status).to.equal(200);\n          expect(savedRequest.participants).to.have.lengthOf(1);\n          expect(savedRequest.participantsAgents).to.have.lengthOf(1);\n          expect(savedRequest.participantsBots).to.have.lengthOf(0);\n          expect(savedRequest.hasBot).to.equal(false);\n          expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n          if (log) { console.log(\"savedRequest.department\", savedRequest.department); }\n          // expect(savedRequest.department.name).to.equal(\"Default\");\n\n          // departmentService.create(\"AssignedDepartment-for-routeDepartmentSameAgentSameDepartmentSkipUpdate\", savedProject._id, 'assigned', userid).then(function(createdDepartment) {\n          let dep = savedRequest.department;\n\n          // route(request_id, departmentid, id_project, nobot, no_populate) {\n          requestService.route(\"routeDepartmentSameAgentSameDepartmentSkipUpdate-\" + now, dep, savedProject._id, false).then(function (routedRequest) {\n\n            expect(routedRequest.request_id).to.equal(\"routeDepartmentSameAgentSameDepartmentSkipUpdate-\" + now);\n            expect(routedRequest.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(routedRequest.first_text).to.equal(\"first_text\");\n            //expect(routedRequest.snapshot.agents).to.have.lengthOf(1);\n            expect(routedRequest.status).to.equal(200);\n            expect(routedRequest.participants).to.have.lengthOf(1);\n            expect(routedRequest.participantsAgents).to.have.lengthOf(1);\n            expect(routedRequest.participantsBots).to.have.lengthOf(0);\n            expect(routedRequest.hasBot).to.equal(false);\n            expect(routedRequest.id_project).to.equal(savedProject._id.toString());\n\n            if (log) { console.log(\"routedRequest.department.name\", routedRequest.department.name); }\n            expect(routedRequest.department._id.toString()).to.equal(dep.toString());\n            //expect(routedRequest.snapshot.department._id.toString()).to.equal(dep.toString());\n\n            done();\n\n          });\n\n        }).catch(function (err) {\n          if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n          assert.isNotOk(err, 'Promise error');\n          done();\n        });\n      });\n    });\n  });\n\n\n\n  // mocha test/requestService.js  --grep 'routeDepartmentSameAgentDifferentDepartment'\n  it('routeDepartmentSameAgentDifferentDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-route-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"routeDepartmentSameAgentDifferentDepartment\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n\n        requestService.createWithIdAndRequester(\"routeDepartmentSameAgentDifferentDepartment-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          winston.debug(\"resolve savedRequest\");\n          expect(savedRequest.request_id).to.equal(\"routeDepartmentSameAgentDifferentDepartment-\" + now);\n          expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n          expect(savedRequest.first_text).to.equal(\"first_text\");\n          //expect(savedRequest.snapshot.agents).to.have.lengthOf(1);\n          expect(savedRequest.status).to.equal(200);\n          expect(savedRequest.participants).to.have.lengthOf(1);\n          expect(savedRequest.participantsAgents).to.have.lengthOf(1);\n          expect(savedRequest.participantsBots).to.have.lengthOf(0);\n          expect(savedRequest.hasBot).to.equal(false);\n          expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n          if (log) { console.log(\"savedRequest.department\", savedRequest.department); }\n          // expect(savedRequest.department.name).to.equal(\"Default\");\n\n          departmentService.create(\"AssignedDepartment-for-routeDepartmentSameAgentDifferentDepartment\", savedProject._id, 'assigned', userid).then(function (createdDepartment) {\n\n            // route(request_id, departmentid, id_project, nobot, no_populate) {\n            requestService.route(\"routeDepartmentSameAgentDifferentDepartment-\" + now, createdDepartment._id, savedProject._id, false).then(function (routedRequest) {\n\n              expect(routedRequest.request_id).to.equal(\"routeDepartmentSameAgentDifferentDepartment-\" + now);\n              expect(routedRequest.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n              expect(routedRequest.first_text).to.equal(\"first_text\");\n              //expect(routedRequest.snapshot.agents).to.have.lengthOf(1);\n              expect(routedRequest.status).to.equal(200);\n              expect(routedRequest.participants).to.have.lengthOf(1);\n              expect(routedRequest.participantsAgents).to.have.lengthOf(1);\n              expect(routedRequest.participantsBots).to.have.lengthOf(0);\n              expect(routedRequest.hasBot).to.equal(false);\n              expect(routedRequest.id_project).to.equal(savedProject._id.toString());\n\n              if (log) { console.log(\"routedRequest.department.name\", routedRequest.department.name); }\n              expect(routedRequest.department._id.toString()).to.equal(createdDepartment._id.toString());\n              //expect(routedRequest.snapshot.department._id.toString()).to.equal(createdDepartment._id.toString());\n\n              done();\n\n            });\n\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n  // mocha test/requestService.js  --grep 'routeDepartmentDifferentAgentDifferentDepartment'\n  it('routeDepartmentDifferentAgentDifferentDepartment', function (done) {\n    // this.timeout(10000);\n\n    var email = \"test-route-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"routeDepartmentDifferentAgentDifferentDepartment\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n\n\n\n        var email2 = \"test-route-create-\" + Date.now() + \"@email.com\";\n        var pwd2 = \"pwd\";\n\n        userService.signup(email2, pwd2, \"Test Firstname\", \"Test lastname\").then(function (savedUser2) {\n          var userid2 = savedUser2.id;\n\n\n          var newProject_user = new Project_user({\n            // _id: new mongoose.Types.ObjectId(),\n            id_project: savedProject._id.toString(),\n            id_user: savedUser2._id.toString(),\n            role: RoleConstants.AGENT,\n            roleType : RoleConstants.TYPE_AGENTS,  \n            user_available: true,\n            createdBy: userid,\n            updatedBy: userid\n          });\n\n          newProject_user.save(function (err, savedProject_user) {\n\n            if (err) {\n              console.log(\"err\", err)\n            }\n            var now = Date.now();\n\n            requestService.createWithIdAndRequester(\"routeDepartmentDifferentAgentDifferentDepartment-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n              winston.debug(\"resolve savedRequest\");\n              expect(savedRequest.request_id).to.equal(\"routeDepartmentDifferentAgentDifferentDepartment-\" + now);\n              expect(savedRequest.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n              expect(savedRequest.first_text).to.equal(\"first_text\");\n              //expect(savedRequest.snapshot.agents).to.have.lengthOf(2);\n              expect(savedRequest.status).to.equal(200);\n              expect(savedRequest.participants).to.have.lengthOf(1);\n              // expect(savedRequest.participants[0]).to.equal(userid);\n              expect(savedRequest.participantsAgents).to.have.lengthOf(1);\n              expect(savedRequest.participantsBots).to.have.lengthOf(0);\n              expect(savedRequest.hasBot).to.equal(false);\n              expect(savedRequest.id_project).to.equal(savedProject._id.toString());\n\n              if (log) { console.log(\"savedRequest.department\", savedRequest.department); }\n              // expect(savedRequest.department.name).to.equal(\"Default\");\n\n\n              var newGroup = new Group({\n                name: \"group1\",\n                members: [userid2],\n                trashed: false,\n                id_project: savedProject._id,\n                createdBy: userid,\n                updatedBy: userid\n              });\n              newGroup.save(function (err, savedGroup) {\n                if (log) { console.log(\"savedGroup\", savedGroup); }\n\n\n\n                departmentService.create(\"AssignedDepartment-for-routeDepartmentDifferentAgentDifferentDepartment\", savedProject._id, 'assigned', userid).then(function (createdDepartment) {\n\n\n                  createdDepartment.id_group = newGroup._id;\n                  createdDepartment.save(function (err, savedGroupDepartment) {\n                    if (log) { console.log(\"savedGroupDepartment\", savedGroupDepartment); }\n\n\n                    // route(request_id, departmentid, id_project, nobot, no_populate) {\n                    requestService.route(\"routeDepartmentDifferentAgentDifferentDepartment-\" + now, createdDepartment._id, savedProject._id, false).then(function (routedRequest) {\n\n                      expect(routedRequest.request_id).to.equal(\"routeDepartmentDifferentAgentDifferentDepartment-\" + now);\n                      expect(routedRequest.requester._id.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n                      expect(routedRequest.first_text).to.equal(\"first_text\");\n                      //expect(routedRequest.snapshot.agents).to.have.lengthOf(1);\n                      expect(routedRequest.status).to.equal(200);\n                      expect(routedRequest.participants).to.have.lengthOf(1);\n                      expect(routedRequest.participants[0]).to.equal(userid2);\n                      expect(routedRequest.participantsAgents).to.have.lengthOf(1);\n                      expect(routedRequest.participantsBots).to.have.lengthOf(0);\n                      expect(routedRequest.hasBot).to.equal(false);\n                      expect(routedRequest.id_project).to.equal(savedProject._id.toString());\n\n                      if (log) { console.log(\"routedRequest.department.name\", routedRequest.department.name); }\n                      expect(routedRequest.department._id.toString()).to.equal(createdDepartment._id.toString());\n                      //expect(routedRequest.snapshot.department._id.toString()).to.equal(createdDepartment._id.toString());\n\n                      done();\n\n                    });\n\n                  });\n                });\n              });\n            });\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n\n\n\n  it('reroute', function (done) {\n\n    var email = \"test-request-reroute-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"reroute-project\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id1-reroute-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n\n          // reroute(request_id, id_project, nobot) {\n          requestService.reroute(savedRequest.request_id, savedProject._id, true).then(function (savedRequestParticipant) {\n            winston.info(\"resolve\", savedRequestParticipant.toObject());\n\n            //savedRequest is assigned -> 200\n            expect(savedRequest.status).to.equal(200);\n\n            //savedRequestParticipant is UNASSIGNED -> 100\n            expect(savedRequestParticipant.request_id).to.equal(\"request_id1-reroute-\" + now);\n            // expect(savedRequestParticipant.requester.toString()).to.equal(savedProjectAndPU.project_user._id.toString());\n            expect(savedRequestParticipant.first_text).to.equal(\"first_text\");\n            //expect(savedRequestParticipant.snapshot.agents).to.have.lengthOf(1);\n            expect(savedRequestParticipant.status).to.equal(200);\n            expect(savedRequestParticipant.participants).to.have.lengthOf(1);\n            expect(savedRequestParticipant.participantsAgents).to.have.lengthOf(1);\n            expect(savedRequestParticipant.participantsAgents).to.contains(userid);\n            expect(savedRequestParticipant.participantsBots).to.have.lengthOf(0);\n\n            expect(savedRequestParticipant.participatingAgents).to.have.lengthOf(1);\n            expect(savedRequestParticipant.participatingBots).to.have.lengthOf(0);\n\n            expect(savedRequestParticipant.id_project).to.equal(savedProject._id.toString());\n\n            done();\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n\n  it('closeRequestAndRemoveParticipant', function (done) {\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"test1\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n        var now = Date.now();\n        requestService.createWithIdAndRequester(\"request_id-closeRequestAndRemoveParticipant-\" + now, savedProjectAndPU.project_user._id, null, savedProject._id, \"first_text\").then(function (savedRequest) {\n          Promise.all([\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello1\",\n              savedProject._id, \"5badfe5d553d1844ad654072\"),\n            messageService.create(\"5badfe5d553d1844ad654072\", \"test sender\", savedRequest.request_id, \"hello2\",\n              savedProject._id, \"5badfe5d553d1844ad654072\")]).then(function (all) {\n                requestService.closeRequestByRequestId(savedRequest.request_id, savedProject._id).then(function (closedRequest) {\n                  expect(closedRequest.closed_at).to.not.equal(null);\n                  expect(closedRequest.transcript).to.contains(\"hello1\");\n                  expect(closedRequest.transcript).to.contains(\"hello2\");\n\n                  requestService.removeParticipantByRequestId(savedRequest.request_id, savedProject._id, userid).then(function (savedRequestParticipant) {\n                    winston.debug(\"resolve closeRequestAndRemoveParticipant\", closedRequest.toObject());\n                    expect(savedRequestParticipant.status).to.equal(1000);\n\n                    done();\n                  });\n                }).catch(function (err) {\n                  winston.error(\"test reject\", err);\n                  assert.isNotOk(err, 'Promise error');\n                  done();\n                });\n              });\n        });\n      });\n    });\n  });\n\n\n\n\n  it('addTag', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          requestService.createWithIdAndRequester(\"request_id1-addTag-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n            winston.debug(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id1-addTag-\" + now);\n            expect(savedRequest.tags.length).to.equal(0);\n\n            var tag = { tag: \"tag1\" };\n            requestService.addTagByRequestId(\"request_id1-addTag-\" + now, savedProject._id, tag).then(function (savedReqTag) {\n              expect(savedReqTag.request_id).to.equal(\"request_id1-addTag-\" + now);\n              expect(savedReqTag.tags.length).to.equal(1);\n              expect(savedReqTag.tags[0].tag).to.equal(\"tag1\");\n              done();\n            });\n\n\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n  it('removeTag', function (done) {\n    // this.timeout(10000);\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          requestService.createWithIdAndRequester(\"request_id1-addTag-\" + now, savedProjectAndPU.project_user._id, createdLead._id, savedProject._id, \"first_text\").then(function (savedRequest) {\n            winston.debug(\"resolve\", savedRequest.toObject());\n            expect(savedRequest.request_id).to.equal(\"request_id1-addTag-\" + now);\n            expect(savedRequest.tags.length).to.equal(0);\n\n            var tag = { tag: \"tag1\" };\n            requestService.addTagByRequestId(\"request_id1-addTag-\" + now, savedProject._id, tag).then(function (savedReqTag) {\n              expect(savedReqTag.request_id).to.equal(\"request_id1-addTag-\" + now);\n              expect(savedReqTag.tags.length).to.equal(1);\n              expect(savedReqTag.tags[0].tag).to.equal(\"tag1\");\n\n              requestService.removeTagByRequestId(\"request_id1-addTag-\" + now, savedProject._id, \"tag1\").then(function (savedReqTagRem) {\n                expect(savedReqTagRem.request_id).to.equal(\"request_id1-addTag-\" + now);\n                expect(savedReqTagRem.tags.length).to.equal(0);\n                done();\n              });\n\n            });\n\n\n          }).catch(function (err) {\n            if (log) { console.log(\"savedRequest.participants[0]\", savedRequest.participants[0]); }\n            assert.isNotOk(err, 'Promise error');\n            done();\n          });\n        });\n      });\n    });\n  });\n\n\n\n\n\n\n\n\n  // mocha test/requestService.js  --grep 'createMessageMicroLanguageAttributes'\n\n  it('createMessageMicroLanguageAttributes', function (done) {\n    // this.timeout(10000);\n\n\n    var microLanguageTransformerInterceptor = require('../pubmodules/messageTransformer/microLanguageAttributesTransformerInterceptor');\n    // var microLanguageTransformerInterceptor = require('../pubmodules/messageTransformer/microLanguageTransformerInterceptor');\n    if (log) { console.log(\"microLanguageTransformerInterceptor\", microLanguageTransformerInterceptor); }\n    microLanguageTransformerInterceptor.listen();\n\n\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n          var now = Date.now();\n          var request = {\n            request_id: \"request_idcreateMessageMicroLanguageAttributes-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            participants: [userid],\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.info(\"resolve\", savedRequest.toObject());\n\n            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata) {\n            messageService.create(userid, \"test sender\", \"testrecipient-createMessageMicroLanguageFromBot\", \"ciao\\n* Button1\",\n              savedProject._id, userid, undefined, { microlanguage: true }).then(function (savedMessage) {\n                winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n                expect(savedMessage.text).to.equal(\"ciao\");\n                expect(savedMessage.type).to.equal(\"text\");\n                expect(savedMessage.attributes._raw_message).to.equal(\"ciao\\n* Button1\", \"attachment\");\n                expect(savedMessage.attributes.attachment.type).to.equal(\"template\");\n                expect(savedMessage.attributes.attachment.buttons[0].value).to.equal(\"Button1\");\n                expect(savedMessage.sender).to.equal(userid);\n                expect(savedMessage.senderFullname).to.equal(\"test sender\");\n                expect(savedMessage.recipient).to.equal(\"testrecipient-createMessageMicroLanguageFromBot\");\n                done();\n\n              })\n          });\n        });\n      });\n\n    });\n  });\n\n\n\n\n\n\n\n\n\n\n\n  // mocha test/requestService.js  --grep 'createMessageMicroLanguageFromBot'\n\n  it('createMessageMicroLanguageFromBot', function (done) {\n    // this.timeout(10000);\n\n\n    // var microLanguageTransformerInterceptor = require('../pubmodules/messageTransformer/microLanguageAttributesTransformerInterceptor');\n    var microLanguageTransformerInterceptor = require('../pubmodules/messageTransformer/microLanguageTransformerInterceptor');\n    if (log) { console.log(\"microLanguageTransformerInterceptor\", microLanguageTransformerInterceptor); }\n    microLanguageTransformerInterceptor.listen();\n\n\n\n    var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      var userid = savedUser.id;\n\n      projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n        var savedProject = savedProjectAndPU.project;\n\n        leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n\n          var now = Date.now();\n\n\n          var request = {\n            request_id: \"request_idcreateMessageMicroLanguageFromBot-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n            id_project: savedProject._id, first_text: \"first_text\",\n            participants: [\"bot_\" + userid],\n            lead: createdLead, requester: savedProjectAndPU.project_user\n          };\n\n\n          requestService.create(request).then(function (savedRequest) {\n            winston.info(\"resolve\", savedRequest.toObject());\n\n            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata) {\n            messageService.create(userid, \"test sender\", \"testrecipient-createMessageMicroLanguageFromBot\", \"ciao\\n* Button1\",\n              savedProject._id, userid, undefined, { microlanguage: true }).then(function (savedMessage) {\n                winston.debug(\"resolve savedMessage\", savedMessage.toObject());\n\n                expect(savedMessage.text).to.equal(\"ciao\");\n                expect(savedMessage.type).to.equal(\"text\");\n                expect(savedMessage.attributes._raw_message).to.equal(\"ciao\\n* Button1\", \"attachment\");\n                expect(savedMessage.attributes.attachment.type).to.equal(\"template\");\n                expect(savedMessage.attributes.attachment.buttons[0].value).to.equal(\"Button1\");\n                expect(savedMessage.sender).to.equal(userid);\n                expect(savedMessage.senderFullname).to.equal(\"test sender\");\n                expect(savedMessage.recipient).to.equal(\"testrecipient-createMessageMicroLanguageFromBot\");\n                done();\n\n              })\n          });\n        });\n      });\n\n    });\n  });\n\n\n\n  // mocha test/requestService.js  --grep 'selectSnapshot'\n\n  // it('selectSnapshot', function (done) {\n  //   // this.timeout(10000);\n  //   // return new Promise(function (resolve) {\n  //   var email = \"test-request-create-\" + Date.now() + \"@email.com\";\n  //   var pwd = \"pwd\";\n\n  //   userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n  //     var userid = savedUser.id;\n\n  //     projectService.createAndReturnProjectAndProjectUser(\"createWithId\", userid).then(function (savedProjectAndPU) {\n  //       var savedProject = savedProjectAndPU.project;\n\n  //       leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n\n  //         var now = Date.now();\n\n  //         var request = {\n  //           request_id: \"request_idselectSnapshot-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n  //           id_project: savedProject._id, first_text: \"first_text\",\n  //           participants: [\"bot_\" + userid],\n  //           lead: createdLead, requester: savedProjectAndPU.project_user\n  //         };\n\n  //         requestService.create(request).then(async function (savedRequest) {\n  //           winston.info(\"resolve\", savedRequest.toObject());\n\n  //           var snapshotAgents = await Request.findById(savedRequest.id).select({ \"snapshot\": 1 }).exec();\n\n  //           if (log) { console.log(\"snapshotAgents\", snapshotAgents); }\n\n  //           expect(snapshotAgents.snapshot.agents.length).to.equal(1);\n  //           // return;\n  //           done();\n\n  //         });\n  //       });\n  //     });\n  //   });\n  //   // });\n  // });\n\n});\n\n\n\n"
  },
  {
    "path": "test/userRequestRoute.js",
    "content": "process.env.NODE_ENV = 'test';\n\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nchai.use(chaiHttp);\n\nvar expect = require('chai').expect;\nvar assert = require('chai').assert;\nvar config = require('../config/database');\n\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nlet log = false;\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\n\nvar userService = require('../services/userService');\nconst projectService = require('../services/projectService');\nvar leadService = require('../services/leadService');\nvar requestService = require('../services/requestService');\nconst faqService = require('../services/faqService');\nvar Bot = require(\"../models/faq_kb\");\n\nvar jwt = require('jsonwebtoken');\nconst uuidv4 = require('uuid/v4');\nconst chatbotTypes = require('../models/chatbotTypes');\n\n\ndescribe('UserService()', function () {\n\n    it('request-rating', function (done) {\n\n        var email = \"test-UserRequest-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            var userid = savedUser.id;\n\n            projectService.createAndReturnProjectAndProjectUser(\"createWithId\", savedUser.id).then(function (savedProjectAndPU) {\n                var savedProject = savedProjectAndPU.project;\n\n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"tilebot\", subtype: chatbotTypes.CHATBOT, language: \"en\", template: \"blank\" }).then(async function (savedFaq_kb) {\n\n                    var signOptions = {\n                        issuer: 'https://tiledesk.com',\n                        subject: 'bot',\n                        audience: 'https://tiledesk.com/bots/' + savedFaq_kb._id,\n                        jwtid: uuidv4()\n                    };\n\n                    let botPayload = savedFaq_kb.toObject();\n                    let botSecret = botPayload.secret;\n\n                    var bot_token = jwt.sign(botPayload, botSecret, signOptions);\n\n                    leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n                        var now = Date.now();\n                        var request = {\n                            request_id: \"request_id-createObjSimple-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n                            id_project: savedProject._id, first_text: \"first_text\",\n                            lead: createdLead, requester: savedProjectAndPU.project_user\n                        };\n\n                        requestService.create(request).then(function (savedRequest) {\n\n                            chai.request(server)\n                                .patch('/' + savedProject._id + '/requests/' + savedRequest.request_id + \"/rating\")\n                                .set('Authorization', \"JWT \" + bot_token)\n                                .send({ rating: 4, rating_message: \"Good\" })\n                                .end((err, res) => {\n\n                                    if (err) { console.error(\"err: \", err) };\n                                    if (log) { console.log(\"res.body: \", res.body) };\n                                    \n                                    res.should.have.status(200);\n                                    res.body.should.be.a('object');\n                                    expect(res.body.rating).to.equal(4);\n                                    expect(res.body.rating_message).to.equal('Good');\n\n                                    done()\n                                });\n                        });\n                    });\n\n\n\n                });\n\n\n\n                // console.log(\"savedProject: \", savedProject)\n                // leadService.createIfNotExists(\"leadfullname\", \"email@email.com\", savedProject._id).then(function (createdLead) {\n                //     var now = Date.now();\n                //     var request = {\n                //         request_id: \"request_id-createObjSimple-\" + now, project_user_id: savedProjectAndPU.project_user._id, lead_id: createdLead._id,\n                //         id_project: savedProject._id, first_text: \"first_text\",\n                //         lead: createdLead, requester: savedProjectAndPU.project_user\n                //     };\n\n                //     console.log(\"request: \", request)\n\n\n                //     requestService.create(request).then(function (savedRequest) {\n\n                //         console.log(\"savedRequest: \", savedRequest);\n                //         done();\n                //     });\n                // });\n            });\n\n        })\n\n\n    }).timeout(10000);\n})\n\n"
  },
  {
    "path": "test/userRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\n\nchai.use(chaiHttp);\n\nvar expect = require('chai').expect;\nvar assert = require('chai').assert;\nvar config = require('../config/database');\n\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nlet log = false;\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\n\nvar userService = require('../services/userService');\nconst projectService = require('../services/projectService');\n\n\ndescribe('UserService()', function () {\n\n  it('loginemail', function (done) {\n    var now = Date.now();\n    var email = \"test-UserService-signup-\" + now + \"@email.com\";\n    var pwd = \"pwd\";\n\n    userService.signup(email , pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n      expect(savedUser.email).to.equal(\"test-userservice-signup-\" + now + \"@email.com\");\n      expect(savedUser.firstname).to.equal( \"Test Firstname\");\n      expect(savedUser.lastname).to.equal(\"Test lastname\");\n      projectService.create(\"test-faqkb-create\", savedUser._id).then(function (savedProject) {\n\n        chai.request(server)\n          .post('/' + savedProject._id + '/faq_kb')\n          .auth(email, pwd)\n          .send({ \"name\": \"testbot\", type: \"internal\", template: \"example\", language: 'en' })\n          .end((err, res) => {\n  \n            if (err) { console.error(\"err: \", err); }\n            if (log) { console.log(\"res.body: \", res.body); }\n  \n            res.should.have.status(200);\n\n            let bot_id = res.body._id;\n  \n            chai.request(server)\n                .post('/users/loginemail')\n                .auth(email, pwd)\n                .send({ \"id_project\": savedProject._id, bot_id: bot_id })\n                .end((err, res) => {\n      \n                  if (err) { console.error(\"err: \", err); }\n                  if (log) { console.log(\"res.body: \", res.body); }\n\n                  res.should.have.status(200);\n                  res.body.should.be.a('object');\n                  expect(res.body.success).to.equal(true);\n                  expect(res.body.message).to.equal(\"Sending email...\");\n\n                  done();\n                })\n          })\n      })\n\n\n\n\n\n    }).catch(function(err) {\n      winston.error(\"test reject\", err);\n      assert.isNotOk(err,'Promise error');\n      done();\n    });\n\n  })\n\n\n});\n\n\n"
  },
  {
    "path": "test/userService.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\nvar expect = require('chai').expect;\n\nvar assert = require('chai').assert;\nvar config = require('../config/database');\n// var Request = require('../models/request');\n// var Requester = require('../models/requester');\n\nvar mongoose = require('mongoose');\nvar winston = require('../config/winston');\n\nlet log = false;\n\n// var databaseUri = process.env.DATABASE_URI || process.env.MONGODB_URI;\n// if (!databaseUri) {\n//   console.log('DATABASE_URI not specified, falling back to localhost.');\n// }\n\n// mongoose.connect(databaseUri || config.database);\nmongoose.connect(config.databasetest);\n\nvar userService = require('../services/userService');\n\n\ndescribe('UserService()', function () {\n\n\n  // it('signup', function (done) {\n  // const myURL =\n  // new URL('https://user:pass@sub.example.com:8080/p/a?query=string#hash');\n  // console.log(myURL.pathname);\n  // console.log(myURL.pathname.split(\"/\")[1]);\n  // });\n\n  it('signup', function (done) {\n\n    var email = \"test-userservice-signup-\" + Date.now() + \"@email.com\";\n\n    userService.signup(email, \"pwd\", \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n\n      if (log) { console.log(\"savedUser resolve\"); }\n      expect(savedUser.email).to.equal(email);\n      expect(savedUser.firstname).to.equal(\"Test Firstname\");\n      expect(savedUser.lastname).to.equal(\"Test lastname\");\n      done();\n    }).catch(function (err) {\n      winston.error(\"test reject\", err);\n      assert.isNotOk(err, 'Promise error');\n      done();\n    });\n  });\n\n\n  it('signupUpperCase', function (done) {\n\n    var now = Date.now();\n    var email = \"test-UserService-signup-\" + now + \"@email.com\";\n\n    userService.signup(email, \"pwd\", \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n      if (log) { console.log(\"savedUser resolve\"); }\n      expect(savedUser.email).to.equal(\"test-userservice-signup-\" + now + \"@email.com\");\n      expect(savedUser.firstname).to.equal(\"Test Firstname\");\n      expect(savedUser.lastname).to.equal(\"Test lastname\");\n      done();\n    }).catch(function (err) {\n      console.error(\"test reject\", err);\n      assert.isNotOk(err, 'Promise error');\n      done();\n    });\n  });\n\n\n  // it('discriminator', function (done) {\n\n  //       var email = \"test-signup-\" + Date.now() + \"@email.com\";\n  //       userService.signup( email ,\"pwd\", \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n  //       // var r = new Request({requester_id: 'test',first_text:'ft',id_project:'123', createdBy: '123', requester: new Requester({ref: savedUser, type:'user'})});\n  //       // var r = new Request({requester_id: 'test',first_text:'ft',id_project:'123', createdBy: '123', requester:savedUser._id});\n  //       var r = new Request({requester_id: 'test',first_text:'ft',id_project:'123', createdBy: '123', requester:savedUser._id, requesterModel:'user'});\n  //       r.save(function(err, request) {\n  //         winston.error(\"test reject\", err);\n  //         winston.info(\"request\", request.toObject());\n  //         Request.findById(request._id).populate('requester').exec(function(err, req1){\n  //           winston.info(\"err\", err);\n  //           winston.info(\"req1\", req1.toObject());\n  //           done();\n  //         });\n\n  //       });\n  //   });\n  // });\n\n\n  // it('discriminator', function (done) {\n\n  //   // var options = {discriminatorKey: 'kind'};\n  //   var options = {};\n\n  //   var eventSchema = new mongoose.Schema({time: Date}, options);\n  //   var Event = mongoose.model('Event', eventSchema);\n\n  //   // ClickedLinkEvent is a special type of Event that has\n  //   // a URL.\n  //   var ClickedLinkEvent = Event.discriminator('ClickedLink',\n  //     new mongoose.Schema({url: String}, options));\n\n  //   // When you create a generic event, it can't have a URL field...\n  //   var genericEvent = new Event({time: Date.now(), url: 'google.com'});\n  //   assert.ok(!genericEvent.url);\n\n  //   // But a ClickedLinkEvent can\n  //   var clickedEvent =\n  //     new ClickedLinkEvent({time: Date.now(), url: 'google.com'});\n  //   assert.ok(clickedEvent.url);\n\n  //   clickedEvent.save(function(err, saved) {\n  //     console.log(\"saved\", err);\n\n  //   });\n\n\n  //   Event.findById('5cc80986176f565ffc210ea6', function(err, getted){\n  //     console.log(\"getted\", getted.toObject());\n  //     console.log(\"instanceof\", getted.toObject() instanceof ClickedLinkEvent);\n  //     console.log(\"instanceof\", getted.toObject() instanceof Event);\n  //     if (getted.toObject() instanceof ClickedLinkEvent) {\n  //       done();\n  //     } else {\n  //       assert.ok(false);\n  //     }\n\n  //   });\n\n\n\n  // });\n\n\n\n\n\n  // it('getUser', function (done) {\n  //   // admin.auth().getUser('5aaa99024c3b110014b478f0')\n  //   // admin.auth().getUser('5bf3cbbc20cb5d0015702910')\n  //   admin.auth().getUser('5b55e806c93dde00143163dd_12345678910')\n\n\n\n  //   .then(function(userRecord) {\n  //     // See the UserRecord reference doc for the contents of userRecord.\n  //     console.log(\"Successfully fetched user data:\", userRecord.toJSON());\n  //     done();\n  //   })\n  //   .catch(function(error) {\n  //     console.log(\"Error fetching user data:\", error);\n  //   });\n  // });\n\n});\n\n\n"
  },
  {
    "path": "test/webhookRoute.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\nprocess.env.LOG_LEVEL = 'error';\nprocess.env.BUCKET_WH_CAPACITY = 2\nprocess.env.BUCKET_WH_REFILL_RATE = 2 / 60;\n\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nlet chatbot_mock = require('./mock/chatbotMock');\nlet log = false;\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nlet should = chai.should();\nvar fs = require('fs');\nconst path = require('path');\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\nchai.use(chaiHttp);\n\ndescribe('WebhookRoute', () => {\n\n    it('create-new-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n                                expect(res.body.async).to.equal(true);\n                                expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                expect(res.body.chatbot_id).to.equal(chatbot_id);\n                                expect(res.body.block_id).to.equal(webhook_intent_id);\n                                expect(res.body.name).to.equal(\"testbot-webhook\");\n                                expect(res.body.enabled).to.equal(true);\n                                should.exist(res.body.webhook_id)\n                                expect(res.body).to.haveOwnProperty('webhook_id')\n                                expect(res.body.webhook_id).to.have.length(32)\n\n                                done();\n\n                            });\n                    });\n            });\n        });\n    })\n\n    it('create-and-get-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                chai.request(server)\n                                    .get('/' + savedProject._id + '/webhooks/' + chatbot_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.async).to.equal(true);\n                                        expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                        expect(res.body.chatbot_id).to.equal(chatbot_id);\n                                        expect(res.body.block_id).to.equal(webhook_intent_id);\n                                        should.exist(res.body.webhook_id)\n                                        expect(res.body).to.haveOwnProperty('webhook_id')\n                                        expect(res.body.webhook_id).to.have.length(32)\n\n                                        let webhook_id = res.body.webhook_id;\n\n                                        chai.request(server)\n                                            .get('/' + savedProject._id + '/webhooks/id/' + webhook_id)\n                                            .auth(email, pwd)\n                                            .end((err, res) => {\n                                                if (err) { console.error(\"err: \", err); }\n                                                if (log) { console.log(\"res.body\", res.body); }\n\n                                                res.should.have.status(200);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.async).to.equal(true);\n                                                expect(res.body.id_project).to.equal(savedProject._id.toString());\n                                                expect(res.body.chatbot_id).to.equal(chatbot_id);\n                                                expect(res.body.block_id).to.equal(webhook_intent_id);\n                                                should.exist(res.body.webhook_id)\n                                                expect(res.body).to.haveOwnProperty('webhook_id')\n                                                expect(res.body.webhook_id).to.have.length(32)\n                                            })\n\n                                    })\n\n                                done();\n\n                            });\n                    });\n            });\n        });\n    })\n\n    it('update-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n                                expect(res.body.async).to.equal(true);\n\n                                chai.request(server)\n                                    .put('/' + savedProject._id + '/webhooks/' + chatbot_id)\n                                    .auth(email, pwd)\n                                    .send({ async: false })\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.async).to.equal(false);\n\n                                        done();\n\n                                    });\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('rigenerate-url-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                let old_webhook_id = res.body.webhook_id;\n\n                                chai.request(server)\n                                    .put('/' + savedProject._id + '/webhooks/' + chatbot_id + \"/regenerate\")\n                                    .auth(email, pwd)\n                                    .send()\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        should.exist(res.body.webhook_id)\n                                        expect(res.body.webhook_id).to.have.length(32)\n                                        expect(res.body.webhook_id).to.not.equal(old_webhook_id);\n\n                                        done();\n\n                                    });\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('delete-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                chai.request(server)\n                                    .delete('/' + savedProject._id + '/webhooks/' + chatbot_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(true);\n                                        expect(res.body.message).to.equal(\"Webhook for chatbot \" + chatbot_id + \" deleted successfully\")\n\n                                        done();\n\n                                    });\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('preload-and-run-webhook', (done) => {\n        \n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-preload\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ name: \"testbot\", type: \"tilebot\", subtype: \"webhook\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                let webhook_id = res.body.webhook_id;\n\n                                chai.request(server)\n                                    .post('/' + savedProject._id + '/webhooks/preload/' + webhook_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(true);\n                                        expect(res.body.message).to.equal(\"Webhook preloaded successfully\");\n                                        assert(res.body.request_id.startsWith('automation-request-' + savedProject._id ))\n\n                                        chai.request(server)\n                                            .post('/webhook/' + webhook_id + \"/dev\")\n                                            .auth(email, pwd)\n                                            .end((err, res) => {\n        \n                                                if (err) { console.error(\"err: \", err); }\n                                                if (log) { console.log(\"res.body\", res.body); }\n        \n                                                res.should.have.status(200);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.success).to.equal(true);\n                                                expect(res.body.message).to.equal(\"Webhook disabled in test mode\");\n        \n\n                                                done();\n        \n                                            });\n                                    })\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('run-webhook-without-preloading', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-preload\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ name: \"testbot\", type: \"tilebot\", subtype: \"webhook\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                let webhook_id = res.body.webhook_id;\n\n                                chai.request(server)\n                                    .post('/webhook/' + webhook_id + \"/dev\")\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(422);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(false);\n                                        expect(res.body.message).to.equal(\"Development webhook is currently turned off\");\n                                        expect(res.body.code).to.equal(13001);\n\n                                        done();\n\n                                    });\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('run-draft-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                let webhook_id = res.body.webhook_id;\n\n                                chai.request(server)\n                                    .post('/webhook/' + webhook_id)\n                                    .auth(email, pwd)\n                                    .end((err, res) => {\n\n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n\n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n                                        expect(res.body.success).to.equal(true);\n                                        expect(res.body.message).to.equal(\"Webhook disabled in test mode\");\n\n                                        done();\n\n                                    });\n                            });\n\n                    });\n            });\n        });\n    })\n\n    it('run-published-webhook', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                class chatbot_service {\n                    async fork(id_faq_kb, api_url, token, project_id) {\n                        let forked_bot_id = id_faq_kb.substr(id_faq_kb, id_faq_kb.length - 4) + \"1111\";\n                        return { bot_id: forked_bot_id }\n                    }\n\n                    async setModified(chatbot_id, modified) {\n                        return true;\n                    }\n                }\n\n                server.set('chatbot_service', new chatbot_service());\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ \"name\": \"testbot\", type: \"tilebot\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .put('/' + savedProject._id + '/faq_kb/' + chatbot_id + '/publish') \n                            .auth(email, pwd)\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                chai.request(server)\n                                    .post('/' + savedProject._id + '/webhooks/')\n                                    .auth(email, pwd)\n                                    .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                                    .end((err, res) => {\n        \n                                        if (err) { console.error(\"err: \", err); }\n                                        if (log) { console.log(\"res.body\", res.body); }\n        \n                                        res.should.have.status(200);\n                                        res.body.should.be.a('object');\n        \n                                        let webhook_id = res.body.webhook_id;\n        \n                                        chai.request(server)\n                                            .post('/webhook/' + webhook_id)\n                                            .auth(email, pwd)\n                                            .end((err, res) => {\n        \n                                                if (err) { console.error(\"err: \", err); }\n                                                if (log) { console.log(\"res.body\", res.body); }\n        \n                                                res.should.have.status(200);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.success).to.equal(true);\n                                                expect(res.body.message).to.equal(\"Webhook disabled in test mode\");\n        \n                                                done();\n        \n                                            });\n                                    });\n\n                            })\n                    });\n            });\n        });\n    })\n\n    it('webhook-rate-limit', (done) => {\n\n        var email = \"test-signup-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n\n        userService.signup(email, pwd, \"Test Firstname\", \"Test lastname\").then(function (savedUser) {\n            projectService.create(\"test-webhook-create\", savedUser._id).then(function (savedProject) {\n\n                chai.request(server)\n                    .post('/' + savedProject._id + '/faq_kb')\n                    .auth(email, pwd)\n                    .send({ name: \"testbot\", type: \"tilebot\", subtype: \"webhook\", language: \"en\", template: \"blank\" })\n                    .end((err, res) => {\n\n                        if (err) { console.error(\"err: \", err); }\n                        if (log) { console.log(\"res.body\", res.body); }\n\n                        res.should.have.status(200);\n                        res.body.should.be.a('object');\n\n                        let chatbot_id = res.body._id;\n                        let webhook_intent_id = \"3bfda939-ff76-4762-bbe0-fc0f0dc4c777\"\n\n                        chai.request(server)\n                            .post('/' + savedProject._id + '/webhooks/')\n                            .auth(email, pwd)\n                            .send({ chatbot_id: chatbot_id, block_id: webhook_intent_id })\n                            .end((err, res) => {\n\n                                if (err) { console.error(\"err: \", err); }\n                                if (log) { console.log(\"res.body\", res.body); }\n\n                                res.should.have.status(200);\n                                res.body.should.be.a('object');\n\n                                let webhook_id = res.body.webhook_id;\n\n                                let iterations = 4;\n                                let interval = 1000;\n                                async function send(i) {\n                                    chai.request(server)\n                                        .post('/webhook/' + webhook_id)\n                                        .auth(email, pwd)\n                                        .end((err, res) => {\n\n                                            if (err) { console.error(\"err: \", err); }\n                                            if (log) { console.log(\"res.body\", res.body); }\n                                            \n                                            if (i !== 3) {\n                                                res.should.have.status(200);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.success).to.equal(true);\n                                                expect(res.body.message).to.equal(\"Webhook disabled in test mode\");\n                                            } else {\n                                                res.should.have.status(429);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.message).to.equal(\"Rate limit exceeded\");\n                                            }\n\n                                            i += 1;\n                                            if (i < iterations) {\n                                                setTimeout(() => {\n                                                    send(i)\n                                                }, interval);\n                                            } else {\n                                                done();\n                                            }\n                                    });\n                                }\n                                send(0);\n\n                        \n                            });\n\n                    });\n            });\n        });\n    }).timeout(6000);\n});\n"
  },
  {
    "path": "test-int/bot.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nlet should = chai.should();\nvar messageService = require('../services/messageService');\nvar requestService = require('../services/requestService');\nvar faqService = require('../services/faqService');\nvar Department = require('../models/department');\nvar Faq = require('../models/faq');\nvar faqBotSupport = require('../services/faqBotSupport');\nvar Project_user = require(\"../models/project_user\");\nvar RoleConstants = require('../models/roleConstants');\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n//server client\nvar express = require('express');\nconst bodyParser = require('body-parser');\n\nvar leadService = require('../services/leadService');\nconst chatbotTypes = require('../models/chatbotTypes');\n\n// var http = require('http');\n// const { parse } = require('querystring');\n\n//end server client\n\nchai.use(chaiHttp);\n\ndescribe('bot', () => {\n\n  describe('/messages', () => {\n \n   \n    // mocha test-int/bot.js  --grep 'createSimpleExatMatch'\n    it('createSimpleExatMatch', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                    \n                    var newFaq = new Faq({\n                        id_faq_kb: savedBot._id,\n                        question: 'question',\n                        answer: 'answer',\n                        id_project: savedProject._id,\n                        createdBy: savedUser._id,\n                        updatedBy: savedUser._id\n                      });\n              \n                                newFaq.save(function (err, savedFaq) {\n\n\n                                Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n\n                                        chai.request(server)\n                                        .post('/'+ savedProject._id + '/subscriptions')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({\"event\":\"message.create\", \"target\":\"http://localhost:3005/\"})\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.event).to.equal(\"message.create\"); \n                                            var secret = res.body.secret;\n                                            expect(secret).to.not.equal(null);                     \n                                            expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                            \n                                        \n                                            let messageReceived = 0;\n                                            var serverClient = express();\n                                            serverClient.use(bodyParser.json());\n                                            serverClient.post('/', function (req, res) {\n                                                console.log('serverClient req', JSON.stringify(req.body));                        \n                                                console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                messageReceived = messageReceived+1;\n                                                expect(req.body.hook.event).to.equal(\"message.create\");\n                                                expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-sending\");\n                                                expect(req.body.payload.request.department).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                \n                                                expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                res.send('POST request to the homepage');\n                                                expect(req.body.payload.text).to.equal(\"answer\");\n                                                // console.log(\"savedFaq\",savedFaq);\n                                                expect(req.body.payload.sender).to.equal(\"bot_\"+savedBot.id);\n                                                expect(req.body.payload.recipient).to.equal(\"request_id-subscription-message-sending\");\n                                                // expect(req.body.payload.attributes._answer._id.toString()).to.equal(savedFaq._id.toString());\n                                                 expect(req.body.payload.attributes._answerid.toString()).to.equal(savedFaq._id.toString());\n\n                                                 expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(false);\n                                                        \n                                                 expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"question\");                                                \n                                                        \n\n                                                done();\n                                                \n                                               \n                                                \n                                                                    \n                                            });\n                                            var listener = serverClient.listen(3005, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                            leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                requestService.createWithId(\"request_id-subscription-message-sending\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                    messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                    savedProject._id, savedUser._id).then(function(savedMessage){\n                                                        expect(savedMessage.text).to.equal(\"question\");     \n                                                        // expect(savedMessage.sender).to.equal(\"question\");     \n                                                    });\n                                                });\n                                            });\n                                        });\n                        });\n                        });\n                    });\n\n            });\n        });\n        }).timeout(20000);\n\n\n\n\n\n    // mocha test-int/bot.js  --grep 'createSimpleAgent'\n    it('createSimpleAgent', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id,  { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                    \n                    var newFaq = new Faq({\n                        id_faq_kb: savedBot._id,\n                        question: 'switch agent',\n                        answer: '\\\\agent',\n                        id_project: savedProject._id,\n                        createdBy: savedUser._id,\n                        updatedBy: savedUser._id\n                      });\n              \n                                newFaq.save(function (err, savedFaq) {\n\n\n                                Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n                                    console.log('000');\n                                        chai.request(server)\n                                        .post('/'+ savedProject._id + '/subscriptions')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({\"event\":\"request.update\", \"target\":\"http://localhost:3006/\"})\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.event).to.equal(\"request.update\"); \n                                            var secret = res.body.secret;\n                                            expect(secret).to.not.equal(null);                     \n                                            expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                            console.log('001');\n\n                                        \n                                            let messageReceived = 0;\n                                            var serverClient = express();\n                                            serverClient.use(bodyParser.json());\n                                            serverClient.post('/', function (req, res) {\n                                                console.log('serverClient req', JSON.stringify(req.body));                        \n                                                console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                messageReceived = messageReceived+1;\n                                                expect(req.body.hook.event).to.equal(\"request.update\");\n                                                console.log('11');\n                                                expect(req.body.payload.request_id).to.equal(\"request_id-subscription-message-sending-createSimpleAgent\");\n                                                console.log('12');\n                                                expect(req.body.payload.hasBot).equal(false);\n                                                console.log('savedUser._id',savedUser._id);                                               \n                                                expect(req.body.payload.participantsAgents[0]).equal(savedUser._id.toString());                                                \n                                                console.log('13');                                               \n                                                \n                                                expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                res.send('POST request to the homepage');\n                                                expect(req.body.payload.first_text).to.equal(\"first_text\");\n                                               \n                                                done();\n                                                \n                                               \n                                                \n                                                                    \n                                            });\n                                            var listener = serverClient.listen(3006, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                            leadService.createIfNotExists(\"leadfullname-subscription-message-sending-createSimpleAgent\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                requestService.createWithId(\"request_id-subscription-message-sending-createSimpleAgent\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                    messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"switch agent\",\n                                                    savedProject._id, savedUser._id).then(function(savedMessage){\n                                                        expect(savedMessage.text).to.equal(\"switch agent\");     \n                                                        // expect(savedMessage.sender).to.equal(\"question\");     \n                                                    });\n                                                });\n                                            });\n                                        });\n                        });\n                        });\n                    });\n\n            });\n        });\n        }).timeout(20000);\n\n\n\n\n \n    // mocha test-int/bot.js  --grep 'createSimpleAgentTwoAgent'\n    it('createSimpleAgentTwoAgent', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n\n            projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {   \n\n            userService.signup( \"test-bot-\" + Date.now() + \"@email.com\" ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser2) {\n\n            var newProject_user = new Project_user({\n                // _id: new mongoose.Types.ObjectId(),\n                id_project: savedProject._id.toString(),\n                id_user: savedUser2._id.toString(),\n                role: RoleConstants.AGENT,\n                roleType : RoleConstants.TYPE_AGENTS,     \n                user_available: true, \n                createdBy: savedUser._id,\n                updatedBy: savedUser._id\n                });\n        \n                return newProject_user.save(function (err, savedProject_user) {\n        \n                if (err) {\n                    console.log(\"err\",err)\n                }\n                leadService.createIfNotExists(\"leadfullname-subscription-message-sending-createSimpleAgent\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                requestService.createWithId(\"request_id-subscription-message-sending-createSimpleAgent-2\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest2) {\n                    console.log(\"savedRequest2\",  savedRequest2);\n\n                    expect(savedRequest2.request_id).to.equal(\"request_id-subscription-message-sending-createSimpleAgent-2\");\n                    // expect(savedRequest2.participantsAgents[0]).equal(savedUser2._id.toString());      \n                    var selectedAgent = savedRequest2.participantsAgents[0];\n                    console.log(\"selectedAgent\",  selectedAgent);\n\n                    expect(savedRequest2.hasBot).equal(false);\n\n                    messageService.create(savedUser._id, \"test sender\", savedRequest2.request_id, \"switch agent\", savedProject._id, savedUser._id).then(function(savedMessage2){\n                        expect(savedMessage2.text).to.equal(\"switch agent\");     \n                        // expect(savedMessage.sender).to.equal(\"question\");     \n                    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                    \n                    var newFaq = new Faq({\n                        id_faq_kb: savedBot._id,\n                        question: 'switch agent',\n                        answer: '\\\\agent',\n                        id_project: savedProject._id,\n                        createdBy: savedUser._id,\n                        updatedBy: savedUser._id\n                      });\n              \n                                newFaq.save(function (err, savedFaq) {\n\n\n                                Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n                                    console.log('000');\n                                        chai.request(server)\n                                        .post('/'+ savedProject._id + '/subscriptions')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({\"event\":\"request.update\", \"target\":\"http://localhost:3021/\"})\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.event).to.equal(\"request.update\"); \n                                            var secret = res.body.secret;\n                                            expect(secret).to.not.equal(null);                     \n                                            expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                            console.log('001');\n\n                                        \n                                            let messageReceived = 0;\n                                            var serverClient = express();\n                                            serverClient.use(bodyParser.json());\n                                            serverClient.post('/', function (req, res) {\n                                                console.log('serverClient req', JSON.stringify(req.body));                        \n                                                console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                messageReceived = messageReceived+1;\n                                                expect(req.body.hook.event).to.equal(\"request.update\");\n                                                console.log('11');\n                                                expect(req.body.payload.request_id).to.equal(\"request_id-subscription-message-sending-createSimpleAgent\");\n                                                console.log('12');\n                                                expect(req.body.payload.hasBot).equal(false);\n                                                console.log('savedUser._id',savedUser._id);                                               \n                                                console.log('savedUser2._id',savedUser2._id);                                               \n                                                // expect(req.body.payload.participantsAgents[0]).equal(savedUser._id.toString());                                                \n                                                expect(req.body.payload.participantsAgents[0]).not.equal(selectedAgent);                                                \n                                                \n                                                console.log('13');                                               \n                                                \n                                                expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                res.send('POST request to the homepage');\n                                                expect(req.body.payload.first_text).to.equal(\"first_text\");\n                                               \n                                                done();\n                                                \n                                               \n                                                \n                                                                    \n                                            });\n                                            var listener = serverClient.listen(3021, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                            \n                                                requestService.createWithId(\"request_id-subscription-message-sending-createSimpleAgent\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                    messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"switch agent\",\n                                                    savedProject._id, savedUser._id).then(function(savedMessage){\n                                                        expect(savedMessage.text).to.equal(\"switch agent\");   \n                                                                                                               \n                                                        // expect(savedMessage.sender).to.equal(\"question\");     \n                                                    });\n                                                });\n                                        });\n                                        });\n                                    });\n                                });\n                        });\n                        });\n                    });\n                });\n            });\n            });\n        });\n        }).timeout(20000);\n       \n\n\n    \n    \n    // mocha test-int/bot.js  --grep 'createSimpleFulltext'\n    it('createSimpleFulltext', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                    \n                    var newFaq = new Faq({\n                        id_faq_kb: savedBot._id,\n                        question: 'question number one',\n                        answer: 'answer',\n                        id_project: savedProject._id,\n                        createdBy: savedUser._id,\n                        updatedBy: savedUser._id\n                      });\n              \n                                newFaq.save(function (err, savedFaq) {\n\n\n                                Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n\n                                        chai.request(server)\n                                        .post('/'+ savedProject._id + '/subscriptions')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({\"event\":\"message.create\", \"target\":\"http://localhost:3010/\"})\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.event).to.equal(\"message.create\"); \n                                            var secret = res.body.secret;\n                                            expect(secret).to.not.equal(null);                     \n                                            expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                            \n                                        \n                                            let messageReceived = 0;\n                                            var serverClient = express();\n                                            serverClient.use(bodyParser.json());\n                                            serverClient.post('/', function (req, res) {\n                                                console.log('serverClient req', JSON.stringify(req.body));                        \n                                                console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                messageReceived = messageReceived+1;\n                                                expect(req.body.hook.event).to.equal(\"message.create\");\n                                                expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-sending\");\n                                                expect(req.body.payload.request.department).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                \n                                                expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                res.send('POST request to the homepage');\n                                                expect(req.body.payload.text).to.equal(\"answer\");\n                                                // console.log(\"savedFaq\",savedFaq);\n                                                expect(req.body.payload.sender).to.equal(\"bot_\"+savedBot.id);\n                                                expect(req.body.payload.recipient).to.equal(\"request_id-subscription-message-sending\");\n                                                // expect(req.body.payload.attributes._answer._id.toString()).to.equal(savedFaq._id.toString());\n                                                 expect(req.body.payload.attributes._answerid.toString()).to.equal(savedFaq._id.toString());\n                                                done();\n                                                \n                                               \n                                                \n                                                                    \n                                            });\n                                            var listener = serverClient.listen(3010, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                            leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                requestService.createWithId(\"request_id-subscription-message-sending\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                    messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                    savedProject._id, savedUser._id).then(function(savedMessage){\n                                                        expect(savedMessage.text).to.equal(\"question\");     \n                                                        // expect(savedMessage.sender).to.equal(\"question\");     \n                                                    });\n                                                });\n                                            });\n                                        });\n                        });\n                        });\n                    });\n\n            });\n        });\n        }).timeout(20000);    \n\n        \n    // mocha test-int/bot.js  --grep 'createSimpleExternalSearcherBot'\n    it('createSimpleExternalSearcherBot', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", url: \"http://localhost:3001/samples/bot/external/searcher\",  type: \"internal\" }).then(function(savedBot) {  \n                    \n                    var newFaq = new Faq({\n                        id_faq_kb: savedBot._id,\n                        question: 'question number one',\n                        answer: 'answer',\n                        id_project: savedProject._id,\n                        createdBy: savedUser._id,\n                        updatedBy: savedUser._id\n                      });\n              \n                                newFaq.save(function (err, savedFaq) {\n\n\n                                Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n\n                                        chai.request(server)\n                                        .post('/'+ savedProject._id + '/subscriptions')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send({\"event\":\"message.create\", \"target\":\"http://localhost:3010/\"})\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.event).to.equal(\"message.create\"); \n                                            var secret = res.body.secret;\n                                            expect(secret).to.not.equal(null);                     \n                                            expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                            \n                                        \n                                            let messageReceived = 0;\n                                            var serverClient = express();\n                                            serverClient.use(bodyParser.json());\n                                            serverClient.post('/', function (req, res) {\n                                                console.log('serverClient req', JSON.stringify(req.body));                        \n                                                console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                messageReceived = messageReceived+1;\n                                                expect(req.body.hook.event).to.equal(\"message.create\");\n                                                expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-sending\");\n                                                expect(req.body.payload.request.department).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                \n                                                expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                res.send('POST request to the homepage');\n                                                expect(req.body.payload.text).to.equal(\"answer\");\n                                                // console.log(\"savedFaq\",savedFaq);\n                                                expect(req.body.payload.sender).to.equal(\"bot_\"+savedBot.id);\n                                                expect(req.body.payload.recipient).to.equal(\"request_id-subscription-message-sending\");\n                                                // expect(req.body.payload.attributes._answer._id.toString()).to.equal(savedFaq._id.toString());\n                                                 expect(req.body.payload.attributes._answerid.toString()).to.equal(savedFaq._id.toString());\n                                                done();\n                                                \n                                               \n                                                \n                                                                    \n                                            });\n                                            var listener = serverClient.listen(3010, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                            leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                requestService.createWithId(\"request_id-subscription-message-sending\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                    messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                    savedProject._id, savedUser._id).then(function(savedMessage){\n                                                        expect(savedMessage.text).to.equal(\"question\");     \n                                                        // expect(savedMessage.sender).to.equal(\"question\");     \n                                                    });\n                                                });\n                                            });\n                                        });\n                        });\n                        });\n                    });\n\n            });\n        });\n        }).timeout(20000);    \n\n\n\n    // mocha test-int/bot.js  --grep 'createNotFoundDefaultFallback'\n\n        it('createNotFoundDefaultFallback', (done) => {\n       \n            var email = \"test-bot-\" + Date.now() + \"@email.com\";\n            var pwd = \"pwd\";\n     \n           \n    \n             userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                 projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                    // create(name, url, projectid, user_id, type) \n                    faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                        \n                        var newFaq = new Faq({\n                            id_faq_kb: savedBot._id,\n                            question: 'question',\n                            answer: 'answer',\n                            id_project: savedProject._id,\n                            createdBy: savedUser._id,\n                            updatedBy: savedUser._id\n                          });\n                  \n                                    newFaq.save(function (err, savedFaq) {\n    \n    \n                                    Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n    \n                                            chai.request(server)\n                                            .post('/'+ savedProject._id + '/subscriptions')\n                                            .auth(email, pwd)\n                                            .set('content-type', 'application/json')\n                                            .send({\"event\":\"message.create\", \"target\":\"http://localhost:3008/\"})\n                                            .end((err, res) => {\n                                                console.log(\"res.body\",  JSON.stringify(res.body));\n                                                // console.dir(\"res.body 1\",  res.body);\n                                                console.log(\"res.headers\",  res.headers);\n                                                res.should.have.status(200);\n                                                res.body.should.be.a('object');\n                                                expect(res.body.event).to.equal(\"message.create\"); \n                                                var secret = res.body.secret;\n                                                expect(secret).to.not.equal(null);                     \n                                                expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                \n                                            \n                                                let messageReceived = 0;\n                                                var serverClient = express();\n                                                serverClient.use(bodyParser.json());\n                                                serverClient.post('/', function (req, res) {\n                                                    console.log('serverClient req', JSON.stringify(req.body));                        \n                                                    console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                    messageReceived = messageReceived+1;\n                                                    \n                                                    expect(req.body.hook.event).to.equal(\"message.create\");\n                                                    expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-sending-createNotFoundDefaultFallback\");\n                                                    \n                                                    expect(req.body.payload.request.department).to.not.equal(null);\n                                                    \n                                                    expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                    \n                                                    expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                    \n                                                    expect(req.headers[\"x-hook-secret\"]).to.equal(secret);\n                                                    \n                                                    expect(req.body.payload.text).to.equal(\"I can not provide an adequate answer. Write a new question or talk to a human agent.\");\n                                                    // expect(req.body.payload.attributes._answer.text).to.equal(\"I can not provide an adequate answer. Write a new question or talk to a human agent.\");\n                                                    expect(req.body.payload.attributes._answerid).to.not.equal(null);\n\n                                                    res.send('POST request to the homepage');\n                                                    \n                                                    done();                                                                                                      \n                                                    \n\n                                                                        \n                                                });\n                                                var listener = serverClient.listen(3008, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n    \n    \n                                                leadService.createIfNotExists(\"leadfullname-subscription-message-sending-createNotFoundDefaultFallback\", \"andrea.leo@-subscription-message-sending-createNotFoundDefaultFallback.it\", savedProject._id).then(function(createdLead) {\n                                                    requestService.createWithId(\"request_id-subscription-message-sending-createNotFoundDefaultFallback\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                        messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"questionNOTFOUND\",\n                                                        savedProject._id, savedUser._id).then(function(savedMessage){\n                                                            expect(savedMessage.text).to.equal(\"questionNOTFOUND\");     \n                                                        });\n                                                    });\n                                                });\n                                            });\n                            });\n                            });\n                        });\n    \n                });\n            });\n            }).timeout(20000);\n    \n    \n\n\n\n\n\n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithImage'\n\n            it('createFaqWithImage', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                            \n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'answer \\n\\\\image:https://www.tiledesk.com/wp-content/uploads/2018/03/tiledesk-logo.png',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                                        newFaq.save(function (err, savedFaq) {\n        \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3011/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"image\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithImage\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"answer\");\n                                                        expect(req.body.payload.metadata.src).to.equal(\"https://www.tiledesk.com/wp-content/uploads/2018/03/tiledesk-logo.png\");\n                                                        expect(req.body.payload.metadata.width).to.equal(200);\n                                                        expect(req.body.payload.metadata.height).to.equal(200);\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(false);\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"question\");\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('answer \\n\\\\image:https://www.tiledesk.com/wp-content/uploads/2018/03/tiledesk-logo.png');\n                                                        \n                                                        done();;\n                                                        \n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3011, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithImage\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"question\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n    \n\n\n\n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithButton'\n\n            it('createFaqWithButton', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                            \n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'answer\\n* Button 1\\n* Button 2',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                                        newFaq.save(function (err, savedFaq) {\n        \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3012/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithButton\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"answer\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(false);\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"question\");\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('answer\\n* Button 1\\n* Button 2');\n                                                        done();;\n                                                        \n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3012, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"question\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithActionButtonFromId'\n\n            it('createFaqWithActionButtonFromId', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                            \n\n                            var newFaq2 = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question2',\n                                answer: 'answer2',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                                });\n\n                            newFaq2.save(function (err, savedFaq2) {              \n\n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'Intro\\n* button1 tdAction:'+savedFaq2._id.toString(),\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                            newFaq.save(function (err, savedFaq) {\n        \n                                                                                    \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3016/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);                                                   \n\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithActionButton\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"answer2\");\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('answer2');\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(false);\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"start action\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                        done();;\n                                                        \n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3016, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithActionButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"start action\",\n                                                            savedProject._id, savedUser._id, undefined, {action: savedFaq2._id.toString()}).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"start action\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                            });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n\n\n\n              \n            // mocha test-int/bot.js  --grep 'createFaqWithActionButtonFromIntentDisplayName'\n\n            it('createFaqWithActionButtonFromIntentDisplayName', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                            \n\n                            var newFaq2 = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question2',\n                                answer: 'answer2',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                                });\n\n                            newFaq2.save(function (err, savedFaq2) {              \n\n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'Intro\\n* button1 tdAction:'+savedFaq2.intent_display_name,\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                            newFaq.save(function (err, savedFaq) {\n        \n                                                                                    \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3017/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);                                                   \n\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithActionButton\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"answer2\");\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('answer2');\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(false);\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"start action\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                        done();;\n                                                        \n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3017, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithActionButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"start action\",\n                                                            savedProject._id, savedUser._id, undefined, {action: savedFaq2._id.toString()}).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"start action\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                            });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n\n  \n\n\n\n            // // mocha test-int/bot.js  --grep 'createFaqWithFrame'\n\n            // it('createFaqWithFrame', (done) => {\n       \n            //     var email = \"test-bot-\" + Date.now() + \"@email.com\";\n            //     var pwd = \"pwd\";\n         \n               \n        \n            //      userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n            //          projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n            //             // create(name, url, projectid, user_id, type) \n            //             faqService.create(\"testbot\", null, savedProject._id, savedUser._id, \"internal\").then(function(savedBot) {  \n                            \n            //                 var newFaq = new Faq({\n            //                     id_faq_kb: savedBot._id,\n            //                     question: 'question',\n            //                     answer: 'answer frame\\\\frame:http://localhost:3013/',\n            //                     id_project: savedProject._id,\n            //                     createdBy: savedUser._id,\n            //                     updatedBy: savedUser._id\n            //                   });\n                      \n            //                             newFaq.save(function (err, savedFaq) {\n        \n        \n            //                             Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n            //                                     chai.request(server)\n            //                                     .post('/'+ savedProject._id + '/subscriptions')\n            //                                     .auth(email, pwd)\n            //                                     .set('content-type', 'application/json')\n            //                                     .send({\"event\":\"message.create\", \"target\":\"http://localhost:3013/\"})\n            //                                     .end((err, res) => {\n            //                                         console.log(\"res.body\",  JSON.stringify(res.body));\n            //                                         // console.dir(\"res.body 1\",  res.body);\n            //                                         console.log(\"res.headers\",  res.headers);\n            //                                         res.should.have.status(200);\n            //                                         res.body.should.be.a('object');\n            //                                         expect(res.body.event).to.equal(\"message.create\"); \n            //                                         var secret = res.body.secret;\n            //                                         expect(secret).to.not.equal(null);                     \n            //                                         expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n            //                                         let messageReceived = 0;\n            //                                         var serverClient = express();\n            //                                         serverClient.use(bodyParser.json());\n            //                                         serverClient.post('/', function (req, res) {\n            //                                             console.log('serverClient req', JSON.stringify(req.body));                        \n            //                                             console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n            //                                             if (req.body.payload.text==\"question\") {\n            //                                                 return res.send('POST request to the homepage');\n            //                                             }\n            //                                             messageReceived = messageReceived+1;\n            //                                             expect(req.body.hook.event).to.equal(\"message.create\");\n            //                                             expect(req.body.payload.type).to.equal(\"frame\");\n            //                                             expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithFrame\");\n            //                                             expect(req.body.payload.request.department).to.not.equal(null);\n            //                                             expect(req.body.payload.request.department.bot).to.not.equal(null);\n            //                                             expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n            //                                             expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n            //                                             res.send('POST request to the homepage');\n            //                                             expect(req.body.payload.text).to.equal(\"answer frame\");\n            //                                             // expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n            //                                             // expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n            //                                             done();;\n                                                        \n                                                      \n                                                                            \n            //                                         });\n            //                                         var listener = serverClient.listen(3013, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n            //                                         leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n            //                                             requestService.createWithId(\"request_id-subscription-message-createFaqWithFrame\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            //                                                 messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n            //                                                 savedProject._id, savedUser._id).then(function(savedMessage){\n            //                                                     expect(savedMessage.text).to.equal(\"question\");     \n            //                                                 });\n            //                                             });\n            //                                         });\n            //                                     });\n            //                     });\n            //                     });\n            //                 });\n        \n            //         });\n            //     });\n            //     }).timeout(20000);\n        \n\n\n\n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithTdFrame'\n\n            it('createFaqWithTdFrame', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\" }).then(function(savedBot) {  \n                            \n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'answer frame\\ntdFrame:http://localhost:3014/',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                                        newFaq.save(function (err, savedFaq) {\n        \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3014/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                   \n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                        messageReceived = messageReceived+1;\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n                                                        \n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"frame\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithTdFrame\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"answer frame\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n                                                        // expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                        done();;\n                                                        \n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3014, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithTdFrame\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"question\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n\n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithWebhook'\n\n            it('createFaqWithWebhook', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\", webhook_url: \"http://localhost:3019/\", webhook_enabled: true }).then(function(savedBot) {  \n                            \n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'answer',\n                                webhook_enabled: true,\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                                        newFaq.save(function (err, savedFaq) {\n        \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3020/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        console.log('1');\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithButton\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        console.log('2');\n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        console.log('3',req.body.payload.text);\n                                                        expect(req.body.payload.text).to.equal(\"ok from webhook\");\n                                                        console.log('4');\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"button1\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                       \n                                                        done();\n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3020, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n                                                    var serverClient2 = express();\n                                                    serverClient2.use(bodyParser.json());\n                                                    serverClient2.post('/', function (req, res) {\n                                                        console.log('serverClient req2', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n                                                        res.send({text:\"ok from webhook\\n* button1\"});                                                       \n                                                    });\n                                                    var listener2 = serverClient2.listen(3019, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"question\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n        \n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithDefaultIntentWebhook'\n\n            it('createFaqWithDefaultIntentWebhook', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\", webhook_url: \"http://localhost:3019/\", webhook_enabled: true }).then(function(savedBot) {  \n                            \n                            Faq.findOneAndUpdate({id_project:savedProject._id,id_faq_kb:savedBot._id, question: \"defaultFallback\" }, {webhook_enabled: true},{new: true, upsert:false}, function (err, savedFaq) {\n                            console.log(\"savedFaq\",savedFaq);\n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3022/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text.indexOf(\"I can not provide an adequate answer\")>-1) {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n                                                        console.log('sono qui');\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithDefaultIntentWebhook\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        expect(req.body.payload.text).to.equal(\"ok from webhook\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"button1\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(true);\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"notfoundword\");\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('ok from webhook\\n* button1');\n                                                        \n                                                       \n                                                        done();\n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3022, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n                                                    var serverClient2 = express();\n                                                    serverClient2.use(bodyParser.json());\n                                                    serverClient2.post('/', function (req, res) {\n                                                        console.log('serverClient req2', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n                                                        res.send({text:\"ok from webhook\\n* button1\"});                                                       \n                                                    });\n                                                    var listener2 = serverClient2.listen(3029, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithDefaultIntentWebhook\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"notfoundword\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"notfoundword\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n                        \n\n\n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithDefaultIntentWebhookReturnAttributes'\n\n            it('createFaqWithDefaultIntentWebhookReturnAttributes', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\", webhook_url: \"http://localhost:3019/\", webhook_enabled: true }).then(function(savedBot) {  \n                            \n                            Faq.findOneAndUpdate({id_project:savedProject._id,id_faq_kb:savedBot._id, question: \"defaultFallback\" }, {webhook_enabled: true},{new: true, upsert:false}, function (err, savedFaq) {\n                            console.log(\"savedFaq\",savedFaq);\n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3023/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                        console.log('sono qui',req.body.payload.text);\n                                                        // if (req.body.payload.text.indexOf(\"I can not provide an adequate answer\")>-1) {\n                                                        //     return res.send('POST request to the homepage');\n                                                        // }\n                                                        \n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        console.log('req.body.payload',req.body.payload);\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        console.log('01');\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithDefaultIntentWebhookReturnAttributes\");\n                                                        console.log('02');\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        console.log('03');\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        console.log('04');\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        console.log('05');\n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n\n                                                        console.log('06');\n                                                        res.send('POST request to the homepage');\n                                                        console.log('07', req.body.payload.text);\n                                                        expect(req.body.payload.text).to.equal(\"ok from webhook with no microlanguage but attributes\");                                                \n                                                        console.log('before attributes',req.body.payload.attributes);\n\n\n                                                        // expect(req.body.payload.channel_type).to.equal(\"group\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        console.log('11');\n                                                        // expect(req.body.payload.language).to.equal(\"IT\");\n                                                        // console.log('22');\n                                                        // expect(req.body.payload.channel.name).to.equal(\"custom-channel\");\n\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"button1\");\n                                                        console.log('33');\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                        console.log('44');\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(true);\n                                                        console.log('55');\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"notfoundword\");\n                                                        console.log('66');\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('ok from webhook with no microlanguage but attributes');\n                                                        console.log('77');\n                                                       \n                                                        done();\n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3023, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n                                                    var serverClient2 = express();\n                                                    serverClient2.use(bodyParser.json());\n                                                    serverClient2.post('/', function (req, res) {\n                                                        console.log('serverClient req2', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n                                                        res.send({text:\"ok from webhook with no microlanguage but attributes\", attributes: {attachment: {buttons: [{value: \"button1\", type:\"text\"}]}}});                                                       \n                                                    });\n                                                    var listener2 = serverClient2.listen(3028, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithDefaultIntentWebhookReturnAttributes\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"notfoundword\",\n                                                            savedProject._id, savedUser._id\n                                                            \n                                                             ,undefined, undefined, undefined, undefined, \"IT\", undefined, \n                                                             //{name:\"custom-channel\"}\n                                                             )\n                                                            .then(function(savedMessage){\n                                                                console.log(\"message saved ok\")\n                                                            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type, channel) {\n                                                            // messageService.save({sender:savedUser._id, senderFullname:\"test sender\",recipient: savedRequest.request_id, text:\"notfoundword\",\n                                                            // id_project:savedProject._id, createdBy: savedUser._id, status: 0, channel:{name:\"custom-channel\"}}).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"notfoundword\");     \n                                                                expect(savedMessage.language).to.equal(\"IT\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n                        \n\n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithDefaultIntentWebhookReturnTypeAndMetadata'\n\n            it('createFaqWithDefaultIntentWebhookReturnTypeAndMetadata', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", type: \"internal\", webhook_url: \"http://localhost:3019/\", webhook_enabled: true }).then(function(savedBot) {  \n                            \n                            Faq.findOneAndUpdate({id_project:savedProject._id,id_faq_kb:savedBot._id, question: \"defaultFallback\" }, {webhook_enabled: true},{new: true, upsert:false}, function (err, savedFaq) {\n                            console.log(\"savedFaq\",savedFaq);\n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3031/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                                        console.log('sono qui',req.body.payload.text);\n                                                        // if (req.body.payload.text.indexOf(\"I can not provide an adequate answer\")>-1) {\n                                                        //     return res.send('POST request to the homepage');\n                                                        // }\n                                                        \n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        console.log('req.body.payload',req.body.payload);\n                                                        expect(req.body.payload.type).to.equal(\"image\");\n                                                        console.log('01');\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithDefaultIntentWebhookReturnAttributes\");\n                                                        console.log('02');\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        console.log('03');\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        console.log('04');\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        console.log('05');\n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n\n                                                        console.log('06');\n                                                        res.send('POST request to the homepage');\n                                                        console.log('07', req.body.payload.text);\n                                                        expect(req.body.payload.text).to.equal(\"ok from webhook with no microlanguage but type and metadata\");                                                \n                                                        console.log('before attributes',req.body.payload.attributes);\n\n\n                                                        // expect(req.body.payload.channel_type).to.equal(\"group\");\n                                                        expect(req.body.payload.type).to.equal(\"image\");\n                                                        console.log('11');\n                                                        // expect(req.body.payload.language).to.equal(\"IT\");\n                                                        // console.log('22');\n                                                        // expect(req.body.payload.channel.name).to.equal(\"custom-channel\");\n\n                                                        expect(req.body.payload.metadata.src).to.equal(\"http://image.jpg\");\n                                                        console.log('44');\n                                                        expect(req.body.payload.attributes.intent_info.is_fallback).to.equal(true);\n                                                        console.log('55');\n                                                        expect(req.body.payload.attributes.intent_info.question_payload.text).to.equal(\"notfoundword\");\n                                                        console.log('66');\n                                                        expect(req.body.payload.attributes._raw_message).to.equal('ok from webhook with no microlanguage but type and metadata');\n                                                        console.log('77');\n                                                       \n                                                        done();\n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3031, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n                                                    var serverClient2 = express();\n                                                    serverClient2.use(bodyParser.json());\n                                                    serverClient2.post('/', function (req, res) {\n                                                        console.log('serverClient req2', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n                                                        res.send({text:\"ok from webhook with no microlanguage but type and metadata\", type: \"image\", metadata: {src: \"http://image.jpg\" }});                                                       \n                                                    });\n                                                    var listener2 = serverClient2.listen(3030, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithDefaultIntentWebhookReturnAttributes\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"notfoundword\",\n                                                            savedProject._id, savedUser._id\n                                                            \n                                                             ,undefined, undefined, undefined, undefined, \"IT\", undefined, \n                                                             //{name:\"custom-channel\"}\n                                                             )\n                                                            .then(function(savedMessage){\n                                                                console.log(\"message saved ok\")\n                                                            // create(sender, senderFullname, recipient, text, id_project, createdBy, status, attributes, type, metadata, language, channel_type, channel) {\n                                                            // messageService.save({sender:savedUser._id, senderFullname:\"test sender\",recipient: savedRequest.request_id, text:\"notfoundword\",\n                                                            // id_project:savedProject._id, createdBy: savedUser._id, status: 0, channel:{name:\"custom-channel\"}}).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"notfoundword\");     \n                                                                expect(savedMessage.language).to.equal(\"IT\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n                        \n\n\n\n\n\n\n            \n\n\n\n            // mocha test-int/bot.js  --grep 'createFaqWithWebhookMicrolanguage'\n\n            it('createFaqWithWebhookMicrolanguage', (done) => {\n       \n                var email = \"test-bot-\" + Date.now() + \"@email.com\";\n                var pwd = \"pwd\";\n         \n               \n        \n                 userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n                     projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                        // create(name, url, projectid, user_id, type) \n                        faqService.create(savedProject._id, savedUser._id,{ name: \"testbot\", type: \"internal\", webhook_enabled: true }).then(function(savedBot) {  \n                            \n                            var newFaq = new Faq({\n                                id_faq_kb: savedBot._id,\n                                question: 'question',\n                                answer: 'answer\\n\\\\webhook:http://localhost:3018/',\n                                id_project: savedProject._id,\n                                createdBy: savedUser._id,\n                                updatedBy: savedUser._id\n                              });\n                      \n                                        newFaq.save(function (err, savedFaq) {\n        \n        \n                                        Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n                                                chai.request(server)\n                                                .post('/'+ savedProject._id + '/subscriptions')\n                                                .auth(email, pwd)\n                                                .set('content-type', 'application/json')\n                                                .send({\"event\":\"message.create\", \"target\":\"http://localhost:3025/\"})\n                                                .end((err, res) => {\n                                                    console.log(\"res.body\",  JSON.stringify(res.body));\n                                                    // console.dir(\"res.body 1\",  res.body);\n                                                    console.log(\"res.headers\",  res.headers);\n                                                    res.should.have.status(200);\n                                                    res.body.should.be.a('object');\n                                                    expect(res.body.event).to.equal(\"message.create\"); \n                                                    var secret = res.body.secret;\n                                                    expect(secret).to.not.equal(null);                     \n                                                    expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n                                                    let messageReceived = 0;\n                                                    var serverClient = express();\n                                                    serverClient.use(bodyParser.json());\n                                                    serverClient.post('/', function (req, res) {\n                                                        console.log('serverClient req', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n                                                        if (req.body.payload.text==\"question\") {\n                                                            return res.send('POST request to the homepage');\n                                                        }\n\n                                                        messageReceived = messageReceived+1;\n                                                        expect(req.body.hook.event).to.equal(\"message.create\");\n                                                        expect(req.body.payload.type).to.equal(\"text\");\n                                                        console.log(\"1\")\n                                                        expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithButton\");\n                                                        expect(req.body.payload.request.department).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot).to.not.equal(null);\n                                                        expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        console.log(\"2\")\n                                                        expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                        res.send('POST request to the homepage');\n                                                        console.log(\"3\",req.body.payload.text)\n                                                        expect(req.body.payload.text).to.equal(\"ok from webhook\");\n                                                        console.log(\"4\")\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"button1\");\n                                                        expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                       \n                                                        done();\n                                                      \n                                                                            \n                                                    });\n                                                    var listener = serverClient.listen(3025, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n                                                    var serverClient2 = express();\n                                                    serverClient2.use(bodyParser.json());\n                                                    serverClient2.post('/', function (req, res) {\n                                                        console.log('serverClient req2', JSON.stringify(req.body));                        \n                                                        console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n                                                        res.send({text:\"ok from webhook\\n* button1\"});\n                                                       \n                                                    });\n                                                    var listener2 = serverClient2.listen(3018, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n                                                    leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                                        requestService.createWithId(\"request_id-subscription-message-createFaqWithButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                                            messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                                            savedProject._id, savedUser._id).then(function(savedMessage){\n                                                                expect(savedMessage.text).to.equal(\"question\");     \n                                                            });\n                                                        });\n                                                    });\n                                                });\n                                });\n                                });\n                            });\n        \n                    });\n                });\n                }).timeout(20000);\n\n            // // mocha test-int/bot.js  --grep 'createFaqWithTdWebhook'\n\n            // it('createFaqWithTdWebhook', (done) => {\n       \n            //     var email = \"test-bot-\" + Date.now() + \"@email.com\";\n            //     var pwd = \"pwd\";\n         \n               \n        \n            //      userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n            //          projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n            //             // create(name, url, projectid, user_id, type) \n            //             faqService.create(\"testbot\", null, savedProject._id, savedUser._id, \"internal\").then(function(savedBot) {  \n                            \n            //                 var newFaq = new Faq({\n            //                     id_faq_kb: savedBot._id,\n            //                     question: 'question',\n            //                     answer: 'answer\\ntdWebhook:http://localhost:3020/',\n            //                     id_project: savedProject._id,\n            //                     createdBy: savedUser._id,\n            //                     updatedBy: savedUser._id\n            //                   });\n                      \n            //                             newFaq.save(function (err, savedFaq) {\n        \n        \n            //                             Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n        \n            //                                     chai.request(server)\n            //                                     .post('/'+ savedProject._id + '/subscriptions')\n            //                                     .auth(email, pwd)\n            //                                     .set('content-type', 'application/json')\n            //                                     .send({\"event\":\"message.create\", \"target\":\"http://localhost:3020/\"})\n            //                                     .end((err, res) => {\n            //                                         console.log(\"res.body\",  JSON.stringify(res.body));\n            //                                         // console.dir(\"res.body 1\",  res.body);\n            //                                         console.log(\"res.headers\",  res.headers);\n            //                                         res.should.have.status(200);\n            //                                         res.body.should.be.a('object');\n            //                                         expect(res.body.event).to.equal(\"message.create\"); \n            //                                         var secret = res.body.secret;\n            //                                         expect(secret).to.not.equal(null);                     \n            //                                         expect(res.headers[\"x-hook-secret\"]).to.equal(secret); \n                                                    \n                                                \n            //                                         let messageReceived = 0;\n            //                                         var serverClient = express();\n            //                                         serverClient.use(bodyParser.json());\n            //                                         serverClient.post('/', function (req, res) {\n            //                                             console.log('serverClient req', JSON.stringify(req.body));                        \n            //                                             console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n\n            //                                             if (req.body.payload.text==\"question\") {\n            //                                                 return res.send('POST request to the homepage');\n            //                                             }\n                                                        \n            //                                             messageReceived = messageReceived+1;\n            //                                             expect(req.body.hook.event).to.equal(\"message.create\");\n            //                                             expect(req.body.payload.type).to.equal(\"text\");\n            //                                             expect(req.body.payload.request.request_id).to.equal(\"request_id-subscription-message-createFaqWithButton\");\n            //                                             expect(req.body.payload.request.department).to.not.equal(null);\n            //                                             expect(req.body.payload.request.department.bot).to.not.equal(null);\n            //                                             expect(req.body.payload.request.department.bot.name).to.equal(\"testbot\");\n                                                        \n            //                                             expect(req.headers[\"x-hook-secret\"]).to.equal(secret); \n            //                                             res.send('POST request to the homepage');\n            //                                             expect(req.body.payload.text).to.equal(\"ok from webhook\");\n            //                                             // expect(req.body.payload.attributes.attachment.buttons[0].value).to.equal(\"Button 1\");\n            //                                             // expect(req.body.payload.attributes.attachment.buttons[0].type).to.equal(\"text\");\n                                                    \n                                                       \n            //                                             done();\n                                                      \n                                                                            \n            //                                         });\n            //                                         var listener = serverClient.listen(3020, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n            //                                         var serverClient2 = express();\n            //                                         serverClient2.use(bodyParser.json());\n            //                                         serverClient2.post('/', function (req, res) {\n            //                                             console.log('serverClient req2', JSON.stringify(req.body));                        \n            //                                             console.log(\"serverClient.headers2\",  JSON.stringify(req.headers));\n            //                                             res.send({text:\"ok from webhook\"});\n                                                       \n            //                                         });\n            //                                         var listener2 = serverClient2.listen(3019, '0.0.0.0', function(){ console.log('Node js Express started', listener2.address());});\n        \n        \n            //                                         leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n            //                                             requestService.createWithId(\"request_id-subscription-message-createFaqWithButton\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n            //                                                 messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n            //                                                 savedProject._id, savedUser._id).then(function(savedMessage){\n            //                                                     expect(savedMessage.text).to.equal(\"question\");     \n            //                                                 });\n            //                                             });\n            //                                         });\n            //                                     });\n            //                     });\n            //                     });\n            //                 });\n        \n            //         });\n            //     });\n            //     }).timeout(20000);\n\n\n\n    });\n});\n\n\n\n\n// 5 failing\n\n// 1) bot\n//      /messages\n//        createFaqWithWebhook:\n//    Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test-int/bot.js)\n//     at listOnTimeout (internal/timers.js:557:17)\n//     at processTimers (internal/timers.js:500:7)\n\n// 2) bot\n//      /messages\n//        createFaqWithDefaultIntentWebhook:\n//    Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test-int/bot.js)\n//     at listOnTimeout (internal/timers.js:557:17)\n//     at processTimers (internal/timers.js:500:7)\n\n// 3) bot\n//      /messages\n//        createFaqWithDefaultIntentWebhookReturnAttributes:\n//    Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test-int/bot.js)\n//     at listOnTimeout (internal/timers.js:557:17)\n//     at processTimers (internal/timers.js:500:7)\n\n// 4) bot\n//      /messages\n//        createFaqWithDefaultIntentWebhookReturnTypeAndMetadata:\n//    Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test-int/bot.js)\n//     at listOnTimeout (internal/timers.js:557:17)\n//     at processTimers (internal/timers.js:500:7)\n\n// 5) bot\n//      /messages\n//        createFaqWithWebhookMicrolanguage:\n//    Error: Timeout of 20000ms exceeded. For async tests and hooks, ensure \"done()\" is called; if returning a Promise, ensure it resolves. (/Users/andrealeo/dev/chat21/tiledesk-server-dev-org/test-int/bot.js)\n//     at listOnTimeout (internal/timers.js:557:17)\n//     at processTimers (internal/timers.js:500:7)\n"
  },
  {
    "path": "test-int/botSubscriptionNotifier.js",
    "content": "//During the test the env variable is set to test\nprocess.env.NODE_ENV = 'test';\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nlet should = chai.should();\nvar messageService = require('../services/messageService');\nvar requestService = require('../services/requestService');\nvar faqService = require('../services/faqService');\nvar Department = require('../models/department');\nvar Faq = require('../models/faq');\nvar faqBotSupport = require('../services/faqBotSupport');\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n//server client\nvar express = require('express');\nconst bodyParser = require('body-parser');\n\nvar leadService = require('../services/leadService');\n\n// var http = require('http');\n// const { parse } = require('querystring');\n\n//end server client\n\nchai.use(chaiHttp);\n\ndescribe('botSubscriptionNotifier', () => {\n \n   \n\n    it('create', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-bot\", savedUser._id).then(function(savedProject) {    \n                // create(name, url, projectid, user_id, type) \n                faqService.create(savedProject._id, savedUser._id, { name: \"testbot\", url: \"http://localhost:3036/\", type: \"external\" }).then(function(savedBot) {  \n                    \n                 \n\n                    Department.findOneAndUpdate({id_project: savedProject._id, default:true}, {id_bot:savedBot._id}, function (err, updatedDepartment) {\n\n                        \n                                \n                            \n                                var serverClient = express();\n                                serverClient.use(bodyParser.json());\n                                serverClient.post('/', function (req, res) {\n                                    console.log('serverClient req', JSON.stringify(req.body));                        \n                                    console.log(\"serverClient.headers\",  JSON.stringify(req.headers));\n                                  \n                                   \n                                \n                                        expect(req.body.payload.text).to.equal(\"question\");\n                                        expect(req.body.hook.name).to.equal(\"testbot\");\n                                        expect(req.body.hook.secret).to.equal(undefined);\n                                        expect(req.body.token).to.not.equal(null);\n                                        done();\n                                        res.send('POST request to the homepage');\n\n                                    \n                                                        \n                                });\n                                var listener = serverClient.listen(3036, '0.0.0.0', function(){ console.log('Node js Express started', listener.address());});\n\n\n                                leadService.createIfNotExists(\"leadfullname-subscription-message-sending\", \"andrea.leo@-subscription-message-sending.it\", savedProject._id).then(function(createdLead) {\n                                    requestService.createWithId(\"request_id-subscription-message-sending\", createdLead._id, savedProject._id, \"first_text\").then(function(savedRequest) {\n                                        messageService.create(savedUser._id, \"test sender\", savedRequest.request_id, \"question\",\n                                        savedProject._id, savedUser._id).then(function(savedMessage){\n                                            expect(savedMessage.text).to.equal(\"question\");     \n                                        });\n                                    });\n                                });\n                            });\n            });\n                      \n                    });\n\n           \n        });\n        }).timeout(20000);\n\n\n\n\n\n\n\n\n});\n"
  },
  {
    "path": "test-int/cache-project.js",
    "content": "\nprocess.env.NODE_ENV = 'test';\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nlet should = chai.should();\nvar Project_user = require(\"../models/project_user\");\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n//server client\nvar express = require('express');\nconst bodyParser = require('body-parser');\n\n//end server client\n\nchai.use(chaiHttp);\n\ndescribe('Cache', () => {\n\n    if (process.env.CACHE_ENABLED == \"true\") {\n    \n    }else {\n        console.log(\"Cache disabled\");\n        expect(true).to.equal(false); \n    }\n  describe('/project', () => {\n \n   \n    // mocha test-int/bot.js  --grep 'createSimpleExatMatch'\n    it('getProject', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-getProject\", savedUser._id).then(function(savedProject) {    \n               \n                                        chai.request(server)\n                                        .get('/projects/'+ savedProject._id + '/')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send()\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.name).to.equal(\"test-getProject\"); \n                                            expect(res.body.status).to.equal(100); \n                                            expect(res.body.isActiveSubscription).to.equal(false); \n                                            expect(res.body.trialDaysLeft).to.equal(-30); \n                                            expect(res.body.trialExpired).to.equal(false); \n                                            \n                                           \n                                        chai.request(server)\n                                        .get('/projects/'+ savedProject._id + '/')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')        //from cache\n                                        .send()\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.name).to.equal(\"test-getProject\"); \n                                            expect(res.body.status).to.equal(100); \n                                            expect(res.body.isActiveSubscription).to.equal(false); \n                                            expect(res.body.trialDaysLeft).to.equal(-30); \n                                            expect(res.body.trialExpired).to.equal(false); \n                                            done()\n                                        });\n            });\n        });\n        });\n\n    });\n  });\n\n});"
  },
  {
    "path": "test-int/cache-project_user.js",
    "content": "\nprocess.env.NODE_ENV = 'test';\n\n\n//Require the dev-dependencies\nlet chai = require('chai');\nlet chaiHttp = require('chai-http');\nlet server = require('../app');\nvar projectService = require('../services/projectService');\nvar userService = require('../services/userService');\nlet should = chai.should();\nvar Project_user = require(\"../models/project_user\");\n\nvar expect = chai.expect;\nvar assert = chai.assert;\n\n//server client\nvar express = require('express');\nconst bodyParser = require('body-parser');\n\n//end server client\n\nchai.use(chaiHttp);\n\ndescribe('Cache', () => {\n\n  if (process.env.CACHE_ENABLED == \"true\") {\n    \n  }else {\n      console.log(\"Cache disabled\");\n      expect(true).to.equal(false); \n  }\n  \n  describe('/project_user', () => {\n \n   \n    // mocha test-int/bot.js  --grep 'createSimpleExatMatch'\n    it('getCurrentProjectUser', (done) => {\n       \n        var email = \"test-bot-\" + Date.now() + \"@email.com\";\n        var pwd = \"pwd\";\n \n       \n\n         userService.signup( email ,pwd, \"Test Firstname\", \"Test lastname\").then(function(savedUser) {\n             projectService.create(\"test-getCurrentProjectUser\", savedUser._id).then(function(savedProject) {    \n               \n                                        chai.request(server)\n                                        .get('/'+ savedProject._id + '/project_users_test/test')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')\n                                        .send()\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.role).to.equal(\"owner\"); \n                                            expect(res.body.isAuthenticated).to.equal(true); \n                                            expect(res.body.id_project).to.equal(savedProject._id.toString()); \n                                            \n                                           \n                                        chai.request(server)\n                                        .get('/'+ savedProject._id + '/project_users_test/test')\n                                        .auth(email, pwd)\n                                        .set('content-type', 'application/json')        //from cache\n                                        .send()\n                                        .end((err, res) => {\n                                            console.log(\"res.body\",  JSON.stringify(res.body));\n                                            // console.dir(\"res.body 1\",  res.body);\n                                            console.log(\"res.headers\",  res.headers);\n                                            res.should.have.status(200);\n                                            res.body.should.be.a('object');\n                                            expect(res.body.role).to.equal(\"owner\"); \n                                            expect(res.body.isAuthenticated).to.equal(true); \n                                            expect(res.body.id_project).to.equal(savedProject._id.toString()); \n                                            \n                                            done()\n                                        });\n            });\n        });\n        });\n\n    });\n  });\n\n});"
  },
  {
    "path": "tiledesk-jmeter.jmx",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<jmeterTestPlan version=\"1.2\" properties=\"5.0\" jmeter=\"5.2.1\">\n  <hashTree>\n    <TestPlan guiclass=\"TestPlanGui\" testclass=\"TestPlan\" testname=\"tiledesk-localhost\" enabled=\"true\">\n      <stringProp name=\"TestPlan.comments\"></stringProp>\n      <boolProp name=\"TestPlan.functional_mode\">false</boolProp>\n      <boolProp name=\"TestPlan.serialize_threadgroups\">false</boolProp>\n      <elementProp name=\"TestPlan.user_defined_variables\" elementType=\"Arguments\" guiclass=\"ArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n        <collectionProp name=\"Arguments.arguments\"/>\n      </elementProp>\n      <stringProp name=\"TestPlan.user_define_classpath\"></stringProp>\n    </TestPlan>\n    <hashTree>\n      <ThreadGroup guiclass=\"ThreadGroupGui\" testclass=\"ThreadGroup\" testname=\"Scenario 1\" enabled=\"true\">\n        <stringProp name=\"ThreadGroup.on_sample_error\">continue</stringProp>\n        <elementProp name=\"ThreadGroup.main_controller\" elementType=\"LoopController\" guiclass=\"LoopControlPanel\" testclass=\"LoopController\" testname=\"Loop Controller\" enabled=\"true\">\n          <boolProp name=\"LoopController.continue_forever\">false</boolProp>\n          <intProp name=\"LoopController.loops\">-1</intProp>\n        </elementProp>\n        <stringProp name=\"ThreadGroup.num_threads\">50</stringProp>\n        <stringProp name=\"ThreadGroup.ramp_time\">5</stringProp>\n        <longProp name=\"ThreadGroup.start_time\">1373789594000</longProp>\n        <longProp name=\"ThreadGroup.end_time\">1373789594000</longProp>\n        <boolProp name=\"ThreadGroup.scheduler\">true</boolProp>\n        <stringProp name=\"ThreadGroup.duration\">300</stringProp>\n        <stringProp name=\"ThreadGroup.delay\">5</stringProp>\n        <stringProp name=\"TestPlan.comments\">Virtual Users Running Scenario 1. \nMake test last 1 minute (see Scheduler)</stringProp>\n        <boolProp name=\"ThreadGroup.same_user_on_next_iteration\">true</boolProp>\n      </ThreadGroup>\n      <hashTree>\n        <ConfigTestElement guiclass=\"HttpDefaultsGui\" testclass=\"ConfigTestElement\" testname=\"HTTP Request Defaults\" enabled=\"true\">\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"User Defined Variables\" enabled=\"true\">\n            <collectionProp name=\"Arguments.arguments\"/>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\">localhost</stringProp>\n          <stringProp name=\"HTTPSampler.port\">3000</stringProp>\n          <stringProp name=\"HTTPSampler.protocol\">http</stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\"></stringProp>\n          <stringProp name=\"TestPlan.comments\">Notice Timeouts:\nRead to 30s\nConnect to 5s</stringProp>\n          <stringProp name=\"HTTPSampler.concurrentPool\">4</stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\">5000</stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\">30000</stringProp>\n        </ConfigTestElement>\n        <hashTree/>\n        <CookieManager guiclass=\"CookiePanel\" testclass=\"CookieManager\" testname=\"HTTP Cookie Manager\" enabled=\"true\">\n          <collectionProp name=\"CookieManager.cookies\"/>\n          <boolProp name=\"CookieManager.clearEachIteration\">false</boolProp>\n          <boolProp name=\"CookieManager.controlledByThreadGroup\">false</boolProp>\n        </CookieManager>\n        <hashTree/>\n        <HeaderManager guiclass=\"HeaderPanel\" testclass=\"HeaderManager\" testname=\"HTTP Header Manager\" enabled=\"true\">\n          <collectionProp name=\"HeaderManager.headers\">\n            <elementProp name=\"User-Agent\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">User-Agent</stringProp>\n              <stringProp name=\"Header.value\"> Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:48.0) Gecko/20100101 Firefox/48.0</stringProp>\n            </elementProp>\n            <elementProp name=\"Accept\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">Accept</stringProp>\n              <stringProp name=\"Header.value\"> text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8</stringProp>\n            </elementProp>\n            <elementProp name=\"Accept-Language\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">Accept-Language</stringProp>\n              <stringProp name=\"Header.value\"> fr,en-US;q=0.7,en;q=0.3</stringProp>\n            </elementProp>\n            <elementProp name=\"Accept-Encoding\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">Accept-Encoding</stringProp>\n              <stringProp name=\"Header.value\"> gzip, deflate</stringProp>\n            </elementProp>\n            <elementProp name=\"\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">Authorization</stringProp>\n              <stringProp name=\"Header.value\">Basic YW5kcmVhLmxlb0BmMjEuaXQ6MTIzNDU2</stringProp>\n            </elementProp>\n            <elementProp name=\"\" elementType=\"Header\">\n              <stringProp name=\"Header.name\">Content-Type</stringProp>\n              <stringProp name=\"Header.value\">application/json</stringProp>\n            </elementProp>\n          </collectionProp>\n        </HeaderManager>\n        <hashTree/>\n        <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Home Page\" enabled=\"true\">\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Variables pré-définies\" enabled=\"true\">\n            <collectionProp name=\"Arguments.arguments\"/>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\"></stringProp>\n          <stringProp name=\"HTTPSampler.port\"></stringProp>\n          <stringProp name=\"HTTPSampler.protocol\"></stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\">/</stringProp>\n          <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n          <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n          <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n        </HTTPSamplerProxy>\n        <hashTree>\n          <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion\" enabled=\"true\">\n            <collectionProp name=\"Asserion.test_strings\">\n              <stringProp name=\"868706650\">Hello from Tiledesk server.</stringProp>\n            </collectionProp>\n            <stringProp name=\"Assertion.test_field\">Assertion.response_data</stringProp>\n            <boolProp name=\"Assertion.assume_success\">false</boolProp>\n            <intProp name=\"Assertion.test_type\">16</intProp>\n            <stringProp name=\"Assertion.custom_message\"></stringProp>\n          </ResponseAssertion>\n          <hashTree/>\n        </hashTree>\n        <TestAction guiclass=\"TestActionGui\" testclass=\"TestAction\" testname=\"ThinkTime1s\" enabled=\"true\">\n          <intProp name=\"ActionProcessor.action\">1</intProp>\n          <intProp name=\"ActionProcessor.target\">0</intProp>\n          <stringProp name=\"ActionProcessor.duration\">0</stringProp>\n        </TestAction>\n        <hashTree>\n          <UniformRandomTimer guiclass=\"UniformRandomTimerGui\" testclass=\"UniformRandomTimer\" testname=\"URT\" enabled=\"true\">\n            <stringProp name=\"ConstantTimer.delay\">1000</stringProp>\n            <stringProp name=\"RandomTimer.range\">100.0</stringProp>\n          </UniformRandomTimer>\n          <hashTree/>\n        </hashTree>\n        <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Page Returning 404\" enabled=\"true\">\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\" guiclass=\"HTTPArgumentsPanel\" testclass=\"Arguments\" testname=\"Variables pré-définies\" enabled=\"true\">\n            <collectionProp name=\"Arguments.arguments\"/>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\"></stringProp>\n          <stringProp name=\"HTTPSampler.port\"></stringProp>\n          <stringProp name=\"HTTPSampler.protocol\"></stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\">/test</stringProp>\n          <stringProp name=\"HTTPSampler.method\">GET</stringProp>\n          <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n          <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n          <stringProp name=\"TestPlan.comments\">It does not fails because we use an assertion that ignores status</stringProp>\n        </HTTPSamplerProxy>\n        <hashTree>\n          <ResponseAssertion guiclass=\"AssertionGui\" testclass=\"ResponseAssertion\" testname=\"Assertion_404\" enabled=\"true\">\n            <collectionProp name=\"Asserion.test_strings\">\n              <stringProp name=\"51512\">404</stringProp>\n            </collectionProp>\n            <stringProp name=\"TestPlan.comments\">The assertion is specia:\n- It ignores status which would make it in error by default (404)\n- It checks Response Code is equal to 404</stringProp>\n            <stringProp name=\"Assertion.test_field\">Assertion.response_code</stringProp>\n            <boolProp name=\"Assertion.assume_success\">true</boolProp>\n            <intProp name=\"Assertion.test_type\">8</intProp>\n            <stringProp name=\"Assertion.custom_message\"></stringProp>\n          </ResponseAssertion>\n          <hashTree/>\n        </hashTree>\n        <HTTPSamplerProxy guiclass=\"HttpTestSampleGui\" testclass=\"HTTPSamplerProxy\" testname=\"Create request\" enabled=\"true\">\n          <boolProp name=\"HTTPSampler.postBodyRaw\">true</boolProp>\n          <elementProp name=\"HTTPsampler.Arguments\" elementType=\"Arguments\">\n            <collectionProp name=\"Arguments.arguments\">\n              <elementProp name=\"\" elementType=\"HTTPArgument\">\n                <boolProp name=\"HTTPArgument.always_encode\">false</boolProp>\n                <stringProp name=\"Argument.value\">{&quot;text&quot;:&quot;firstText&quot;}</stringProp>\n                <stringProp name=\"Argument.metadata\">=</stringProp>\n              </elementProp>\n            </collectionProp>\n          </elementProp>\n          <stringProp name=\"HTTPSampler.domain\"></stringProp>\n          <stringProp name=\"HTTPSampler.port\"></stringProp>\n          <stringProp name=\"HTTPSampler.protocol\"></stringProp>\n          <stringProp name=\"HTTPSampler.contentEncoding\"></stringProp>\n          <stringProp name=\"HTTPSampler.path\">/5ea800091147f28c72b90c5e/requests</stringProp>\n          <stringProp name=\"HTTPSampler.method\">POST</stringProp>\n          <boolProp name=\"HTTPSampler.follow_redirects\">true</boolProp>\n          <boolProp name=\"HTTPSampler.auto_redirects\">false</boolProp>\n          <boolProp name=\"HTTPSampler.use_keepalive\">true</boolProp>\n          <boolProp name=\"HTTPSampler.DO_MULTIPART_POST\">false</boolProp>\n          <stringProp name=\"HTTPSampler.embedded_url_re\"></stringProp>\n          <stringProp name=\"HTTPSampler.connect_timeout\"></stringProp>\n          <stringProp name=\"HTTPSampler.response_timeout\"></stringProp>\n        </HTTPSamplerProxy>\n        <hashTree/>\n      </hashTree>\n      <ResultCollector guiclass=\"ViewResultsFullVisualizer\" testclass=\"ResultCollector\" testname=\"View Results Tree\" enabled=\"true\">\n        <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n        <objProp>\n          <name>saveConfig</name>\n          <value class=\"SampleSaveConfiguration\">\n            <time>true</time>\n            <latency>true</latency>\n            <timestamp>true</timestamp>\n            <success>true</success>\n            <label>true</label>\n            <code>true</code>\n            <message>true</message>\n            <threadName>true</threadName>\n            <dataType>false</dataType>\n            <encoding>false</encoding>\n            <assertions>true</assertions>\n            <subresults>true</subresults>\n            <responseData>false</responseData>\n            <samplerData>false</samplerData>\n            <xml>false</xml>\n            <fieldNames>true</fieldNames>\n            <responseHeaders>false</responseHeaders>\n            <requestHeaders>false</requestHeaders>\n            <responseDataOnError>false</responseDataOnError>\n            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n            <assertionsResultsToSave>0</assertionsResultsToSave>\n            <bytes>true</bytes>\n            <threadCounts>true</threadCounts>\n            <idleTime>true</idleTime>\n            <connectTime>true</connectTime>\n          </value>\n        </objProp>\n        <stringProp name=\"TestPlan.comments\">For scripting only</stringProp>\n        <stringProp name=\"filename\"></stringProp>\n      </ResultCollector>\n      <hashTree/>\n      <ResultCollector guiclass=\"SummaryReport\" testclass=\"ResultCollector\" testname=\"Summary Report\" enabled=\"false\">\n        <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n        <objProp>\n          <name>saveConfig</name>\n          <value class=\"SampleSaveConfiguration\">\n            <time>true</time>\n            <latency>true</latency>\n            <timestamp>true</timestamp>\n            <success>true</success>\n            <label>true</label>\n            <code>true</code>\n            <message>true</message>\n            <threadName>true</threadName>\n            <dataType>true</dataType>\n            <encoding>false</encoding>\n            <assertions>true</assertions>\n            <subresults>true</subresults>\n            <responseData>false</responseData>\n            <samplerData>false</samplerData>\n            <xml>false</xml>\n            <fieldNames>true</fieldNames>\n            <responseHeaders>false</responseHeaders>\n            <requestHeaders>false</requestHeaders>\n            <responseDataOnError>false</responseDataOnError>\n            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n            <assertionsResultsToSave>0</assertionsResultsToSave>\n            <bytes>true</bytes>\n            <sentBytes>true</sentBytes>\n            <url>true</url>\n            <threadCounts>true</threadCounts>\n            <idleTime>true</idleTime>\n            <connectTime>true</connectTime>\n          </value>\n        </objProp>\n        <stringProp name=\"filename\"></stringProp>\n      </ResultCollector>\n      <hashTree/>\n      <ResultCollector guiclass=\"GraphVisualizer\" testclass=\"ResultCollector\" testname=\"Graph Results\" enabled=\"true\">\n        <boolProp name=\"ResultCollector.error_logging\">false</boolProp>\n        <objProp>\n          <name>saveConfig</name>\n          <value class=\"SampleSaveConfiguration\">\n            <time>true</time>\n            <latency>true</latency>\n            <timestamp>true</timestamp>\n            <success>true</success>\n            <label>true</label>\n            <code>true</code>\n            <message>true</message>\n            <threadName>true</threadName>\n            <dataType>true</dataType>\n            <encoding>false</encoding>\n            <assertions>true</assertions>\n            <subresults>true</subresults>\n            <responseData>false</responseData>\n            <samplerData>false</samplerData>\n            <xml>false</xml>\n            <fieldNames>true</fieldNames>\n            <responseHeaders>false</responseHeaders>\n            <requestHeaders>false</requestHeaders>\n            <responseDataOnError>false</responseDataOnError>\n            <saveAssertionResultsFailureMessage>true</saveAssertionResultsFailureMessage>\n            <assertionsResultsToSave>0</assertionsResultsToSave>\n            <bytes>true</bytes>\n            <sentBytes>true</sentBytes>\n            <url>true</url>\n            <threadCounts>true</threadCounts>\n            <idleTime>true</idleTime>\n            <connectTime>true</connectTime>\n          </value>\n        </objProp>\n        <stringProp name=\"filename\"></stringProp>\n      </ResultCollector>\n      <hashTree/>\n    </hashTree>\n  </hashTree>\n</jmeterTestPlan>\n"
  },
  {
    "path": "utils/TdCache.js",
    "content": "const redis = require('redis');\n\nclass TdCache {\n\n    constructor(config) {\n        this.redis_host = config.host;\n        this.redis_port = config.port;\n        this.redis_password = config.password;\n        this.client = null;\n    }\n\n    async connect(callback) {\n        // client = redis.createClient();\n        return new Promise( async (resolve, reject) => {\n            this.client = redis.createClient(\n                {\n                    host: this.redis_host,\n                    port: this.redis_port,\n                    password: this.redis_password\n                });\n            this.client.on('error', err => {\n                reject(err);\n                if (callback) {\n                    callback(err);\n                }\n            });\n            // this.client.on('connect', function() {\n            //     console.log('Redis Connected!');\n            // });\n            this.client.on('ready',function() {\n                resolve();\n                // onsole.log(\"Redis is ready.\");\n                if (callback) {\n                    callback();\n                }\n                //console.log(\"Redis is ready.\");\n            });\n        });\n    }\n\n    async set(key, value, options) {\n      //console.log(\"setting key value\", key, value)\n      if (!options) {\n        options = {EX: 86400}\n      }\n      return new Promise( async (resolve, reject) => {\n        if (options && options.EX) {\n          //console.log(\"expires:\", options.EX)\n          try {\n            await this.client.set(\n              key,\n              value,\n              'EX', options.EX);\n          }\n          catch(error) {\n            reject(error)\n          }\n        }\n        else {\n          try {\n            //console.log(\"setting here...key\", key, value)\n            await this.client.set(\n              key,\n              value);\n          }\n          catch(error) {\n            console.error(\"Error\", error);\n            reject(error)\n          }\n        }\n        if (options && options.callback) {\n            options.callback();\n        }\n        //console.log(\"resolving...\", key);\n        return resolve();\n      });\n    }\n\n    /**\n     * Set key only if it does not exist, with TTL (seconds). Returns true if key was set, false if key already existed.\n     * Uses Redis SET key value EX ttl NX to avoid email/notification flooding.\n     */\n    async setNX(key, value, ttlSeconds) {\n      return new Promise((resolve, reject) => {\n        this.client.set(key, value, 'EX', ttlSeconds, 'NX', (err, reply) => {\n          if (err) return reject(err);\n          resolve(reply === 'OK');\n        });\n      });\n    }\n\n\n\n\n    async incr(key) {\n      // console.log(\"incr key:\", key)\n      return new Promise( async (resolve, reject) => {\n        try {\n            // console.log(\"incr here...key\", key)\n            await this.client.incr(key);\n          }\n          catch(error) {\n            console.error(\"Error on incr:\", error);\n            reject(error)\n          }\n        return resolve();\n      });\n    }\n\n    async incrby(key, increment) {\n      return new Promise( async (resolve, reject) => {\n        try {\n          await this.client.incrby(key, increment);\n        }\n        catch(error) {\n          console.error(\"Error on incrby:\", error);\n          reject(error)\n        }\n        return resolve()\n      })\n    }\n\n    async incrbyfloat(key, increment) {\n      return new Promise( async (resolve, reject) => {\n        try {\n          await this.client.incrbyfloat(key, increment);\n        }\n        catch(error) {\n          reject(error);\n        }\n        return resolve();\n      })\n    }\n\n    async hset(dict_key, key, value, options) {\n      //console.log(\"hsetting dict_key key value\", dict_key, key, value)\n      return new Promise( async (resolve, reject) => {\n        if (options && options.EX) {\n          //console.log(\"expires:\", options.EX)\n          try {\n            await this.client.hset(\n              dict_key,\n              key,\n              value,\n              'EX', options.EX);\n          }\n          catch(error) {\n            reject(error)\n          }\n        }\n        else {\n          try {\n            //console.log(\"setting here...key\", key, value)\n            await this.client.hset(\n              dict_key,\n              key,\n              value);\n          }\n          catch(error) {\n            console.error(\"Error\", error);\n            reject(error)\n          }\n        }\n        if (options && options.callback) {\n            options.callback();\n        }\n        return resolve();\n      });\n    }\n\n    async hdel(dict_key, key, options) {\n      //console.log(\"hsetting dict_key key value\", dict_key, key, value)\n      return new Promise( async (resolve, reject) => {\n        if (options && options.EX) {\n          //console.log(\"expires:\", options.EX)\n          try {\n            await this.client.hdel(\n              dict_key,\n              key,\n              'EX', options.EX);\n          }\n          catch(error) {\n            reject(error)\n          }\n        }\n        else {\n          try {\n            //console.log(\"setting here...key\", key, value)\n            await this.client.hdel(\n              dict_key,\n              key);\n          }\n          catch(error) {\n            console.error(\"Error\", error);\n            reject(error);\n          }\n        }\n        if (options && options.callback) {\n            options.callback();\n        }\n        return resolve();\n      });\n    }\n    \n    async setJSON(key, value, options) {\n      const _string = JSON.stringify(value);\n      return await this.set(key, _string, options);\n    }\n    \n    async get(key, callback) {\n      return new Promise( async (resolve, reject) => {\n        this.client.get(key, (err, value) => {\n          if (err) {\n            reject(err);\n          }\n          else {\n            if (callback) {\n              callback(value);\n          }\n          return resolve(value);\n          }\n        });\n      });\n    }\n\n    async hgetall(dict_key, callback) {\n      //console.log(\"hgetting dics\", dict_key);\n      return new Promise( async (resolve, reject) => {\n        this.client.hgetall(dict_key, (err, value) => {\n          if (err) {\n            reject(err);\n            if (callback) {\n              callback(err, null);\n            }\n          }\n          else {\n            if (callback) {\n              callback(null, value);\n            }\n            resolve(value);\n          }\n        });\n      });\n    }\n\n    async hget(dict_key, key, callback) {\n      //console.log(\"hgetting dics\", dict_key);\n      return new Promise( async (resolve, reject) => {\n        this.client.hget(dict_key, key, (err, value) => {\n          if (err) {\n            reject(err);\n            if (callback) {\n              callback(err, null);\n            }\n          }\n          else {\n            if (callback) {\n              callback(null, value);\n            }\n            resolve(value);\n          }\n        });\n      });\n    }\n    \n    async getJSON(key, callback) {\n      const value = await this.get(key);\n      return JSON.parse(value);\n    }\n    \n    async del(key, callback) {\n      return new Promise( async (resolve, reject) => {\n        let result = await this.client.del(key);\n        if (callback) {\n            callback(result);\n        }\n        return resolve(result);\n      })\n    }\n\n\n    getClient() {\n      return this.client;\n    }\n}\n\nmodule.exports = { TdCache };"
  },
  {
    "path": "utils/UIDGenerator.js",
    "content": "let stringUtil = require(\"../utils/stringUtil\");\nconst uuidv4 = require('uuid/v4');\n\nclass UIDGenerator {\n\n    generate() {        \n        let uid = uuidv4();\n        // console.log(\"uid\",uid);\n        let uidWithoutChar = stringUtil.replaceAll(uid, \"-\", \"\");\n        // console.log(\"uidWithoutChar\",uidWithoutChar);\n        return uidWithoutChar;\n    }\n}\n\n var uidGenerator = new UIDGenerator();\n\n module.exports = uidGenerator;\n \n\n "
  },
  {
    "path": "utils/aiUtils.js",
    "content": "var winston = require('../config/winston');\n\n// MODELS_MULTIPLIER = {\n//     \"gpt-3.5-turbo\": 0.6,\n//     \"gpt-4\": 25,\n//     \"gpt-4-turbo-preview\": 12\n// }\n\nloadMultiplier();\nfunction loadMultiplier() {\n    \n\n    let models_string = process.env.AI_MODELS;\n    winston.debug(\"(loadMultiplier) models_string: \", models_string)\n    let models = {};\n\n    if (!models_string) {\n        winston.info(\"AI_MODELS not defined\");\n        winston.info(\"AI Models: \", models)\n        return models;\n    }\n\n    let models_string_trimmed = models_string.replace(/ /g,'');\n    winston.debug(\"(loadMultiplier) models_string_trimmed: \", models_string_trimmed)\n\n    let splitted_string = models_string_trimmed.split(\";\");\n    winston.debug(\"splitted_string: \", splitted_string)\n\n    splitted_string.forEach(m => {\n        m_split = m.split(\":\");\n        if (!m_split[1]) {\n            multiplier = null;\n        } else {\n            multiplier = Number(m_split[1]);;\n        }\n        models[m_split[0]] = multiplier;\n    })\n\n    winston.info(\"AI Models: \", models)\n    return models;\n}\n\nmodule.exports = { MODELS_MULTIPLIER: loadMultiplier() }"
  },
  {
    "path": "utils/arrayUtil.js",
    "content": "// unused\nfunction arraysEqual(a, b) {\n    if (a === b) return true;\n    if (a == null || b == null) return false;\n    if (a.length != b.length) return false;\n  \n    // If you don't care about the order of the elements inside\n    // the array, you should sort both arrays here.\n    // Please note that calling sort on an array will modify that array.\n    // you might want to clone your array first.\n  \n    for (var i = 0; i < a.length; ++i) {\n      if (a[i] !== b[i]) return false;\n    }\n    return true;\n  }\n\n/**\n * Parse a field as an array of strings.\n * Returns the array if valid, otherwise undefined.\n */\nfunction parseStringArrayField(field) {\n  if (!field) return undefined;\n\n  let arr;\n\n  if (typeof field === 'string') {\n    try {\n      arr = JSON.parse(field); // prova a parsare come JSON\n    } catch {\n      return undefined; // se malformato, skippa\n    }\n  } else if (Array.isArray(field)) {\n    arr = field;\n  } else {\n    return undefined; // non stringa né array\n  }\n\n  // controlla che tutti gli elementi siano stringhe\n  if (Array.isArray(arr) && arr.every(e => typeof e === 'string')) {\n    return arr;\n  }\n\n  return undefined; // non è un array di stringhe\n}\n\nmodule.exports = {\n  arraysEqual,\n  parseStringArrayField\n};"
  },
  {
    "path": "utils/autoIncrement.js",
    "content": "// Module Scope\nvar mongoose = require('mongoose'),\nextend = require('extend'),\ncounterSchema,\nIdentityCounter;\n\n// Initialize plugin by creating counter collection in database.\nexports.initialize = function (connection) {\n  try {\n    IdentityCounter = connection.model('IdentityCounter');\n  } catch (ex) {\n    if (ex.name === 'MissingSchemaError') {\n      // Create new counter schema.\n      counterSchema = new mongoose.Schema({\n        model: { type: String, require: true },\n        field: { type: String, require: true },\n        count: { type: Number, default: 0 }\n      });\n\n      // Create a unique index using the \"field\" and \"model\" fields.\n      counterSchema.index({ field: 1, model: 1 }, { unique: true, required: true, index: -1 });\n\n      // Create model using new schema.\n      IdentityCounter = connection.model('IdentityCounter', counterSchema);\n    }\n    else\n      throw ex;\n  }\n};\n\n// The function to use when invoking the plugin on a custom schema.\nexports.plugin = function (schema, options) {\n\n  // If we don't have reference to the counterSchema or the IdentityCounter model then the plugin was most likely not\n  // initialized properly so throw an error.\n  if (!counterSchema || !IdentityCounter) throw new Error(\"mongoose-auto-increment has not been initialized\");\n\n  // Default settings and plugin scope variables.\n  var settings = {\n    model: null, // The model to configure the plugin for.\n    field: '_id', // The field the plugin should track.\n    startAt: 0, // The number the count should start at.\n    incrementBy: 1, // The number by which to increment the count each time.\n    unique: true // Should we create a unique index for the field\n  },\n  fields = {}, // A hash of fields to add properties to in Mongoose.\n  ready = false; // True if the counter collection has been updated and the document is ready to be saved.\n\n  switch (typeof(options)) {\n    // If string, the user chose to pass in just the model name.\n    case 'string':\n      settings.model = options;\n    break;\n    // If object, the user passed in a hash of options.\n    case 'object':\n      extend(settings, options);\n    break;\n  }\n\n  if (settings.model == null)\n    throw new Error(\"model must be set\");\n\n  // Add properties for field in schema.\n  fields[settings.field] = {\n    type: Number,\n    require: true\n  };\n  if (settings.field !== '_id')\n    fields[settings.field].unique = settings.unique\n  schema.add(fields);\n\n  // Find the counter for this model and the relevant field.\n  IdentityCounter.findOne(\n    { model: settings.model, field: settings.field },\n    function (err, counter) {\n      if (!counter) {\n        // If no counter exists then create one and save it.\n        counter = new IdentityCounter({ model: settings.model, field: settings.field, count: settings.startAt - settings.incrementBy });\n        counter.save(function () {\n          ready = true;\n        });\n      }\n      else {\n        ready = true;\n      }\n    }\n  );\n\n  // Declare a function to get the next counter for the model/schema.\n  var nextCount = function (callback) {\n    IdentityCounter.findOne({\n      model: settings.model,\n      field: settings.field\n    }, function (err, counter) {\n      if (err) return callback(err);\n      callback(null, counter === null ? settings.startAt : counter.count + settings.incrementBy);\n    });\n  };\n  // Add nextCount as both a method on documents and a static on the schema for convenience.\n  schema.method('nextCount', nextCount);\n  schema.static('nextCount', nextCount);\n\n  // Declare a function to reset counter at the start value - increment value.\n  var resetCount = function (callback) {\n    IdentityCounter.findOneAndUpdate(\n      { model: settings.model, field: settings.field },\n      { count: settings.startAt - settings.incrementBy },\n      { new: true }, // new: true specifies that the callback should get the updated counter.\n      function (err) {\n        if (err) return callback(err);\n        callback(null, settings.startAt);\n      }\n    );\n  };\n  // Add resetCount as both a method on documents and a static on the schema for convenience.\n  schema.method('resetCount', resetCount);\n  schema.static('resetCount', resetCount);\n\n  // Every time documents in this schema are saved, run this logic.\n  schema.pre('save', function (next) {\n    // Get reference to the document being saved.\n    var doc = this;\n\n    // Only do this if it is a new document (see http://mongoosejs.com/docs/api.html#document_Document-isNew)\n    if (doc.isNew) {\n      // Declare self-invoking save function.\n      (function save() {\n        // If ready, run increment logic.\n        // Note: ready is true when an existing counter collection is found or after it is created for the\n        // first time.\n        if (ready) {\n          // check that a number has already been provided, and update the counter to that number if it is\n          // greater than the current count\n          if (typeof doc[settings.field] === 'number') {\n              console.log(\"docid_project\",doc[\"id_project\"])\n            IdentityCounter.findOneAndUpdate(\n              // IdentityCounter documents are identified by the model and field that the plugin was invoked for.\n              // Check also that count is less than field value.\n              { model: settings.model+doc[\"id_project\"], field: settings.field, count: { $lt: doc[settings.field] } },\n              // Change the count of the value found to the new field value.\n              { count: doc[settings.field] },\n              function (err) {\n                if (err) return next(err);\n                // Continue with default document save functionality.\n                next();\n              }\n            );\n          } else {\n            console.log(\"docid_project\",doc[\"id_project\"])\n            // Find the counter collection entry for this model and field and update it.\n            IdentityCounter.findOneAndUpdate(\n              // IdentityCounter documents are identified by the model and field that the plugin was invoked for.\n              { model: settings.model+doc[\"id_project\"], field: settings.field },\n              // Increment the count by `incrementBy`.\n              { $inc: { count: settings.incrementBy } },\n              // new:true specifies that the callback should get the counter AFTER it is updated (incremented).\n              { new: true },\n              // Receive the updated counter.\n              function (err, updatedIdentityCounter) {\n                if (err) return next(err);\n                // If there are no errors then go ahead and set the document's field to the current count.\n                doc[settings.field] = updatedIdentityCounter.count;\n                // Continue with default document save functionality.\n                next();\n              }\n            );\n          }\n        }\n        // If not ready then set a 5 millisecond timer and try to save again. It will keep doing this until\n        // the counter collection is ready.\n        else\n          setTimeout(save, 5);\n      })();\n    }\n    // If the document does not have the field we're interested in or that field isn't a number AND the user did\n    // not specify that we should increment on updates, then just continue the save without any increment logic.\n    else\n      next();\n  });\n};\n"
  },
  {
    "path": "utils/botFromParticipant.js",
    "content": "var winston = require('../config/winston');\n\n\nclass BotFromParticipant {\n\n     //TODO use request. getBotId\n getBotFromParticipants(participants) {\n    var botIdTmp;\n  \n    if (participants) {\n      participants.forEach(function(participant) { \n        //winston.debug(\"participant\", participant);\n        // botprefix\n        if (participant.indexOf(\"bot_\")> -1) {\n          // botprefix\n          botIdTmp = participant.replace(\"bot_\",\"\");\n          //winston.debug(\"botIdTmp\", botIdTmp);\n          //break;        \n        }\n      });\n    \n      return botIdTmp;\n    }else {\n      return null;\n    }\n  }\n\n//TODO use request. getBotId\n getBotId(message) {\n    var sender = message.sender;\n    winston.debug(\"sender\", sender);\n \n    if (sender==\"sytem\") {\n         return null;\n    }\n \n    var recipient = message.recipient;\n    winston.debug(\"recipient\", recipient);\n \n    // botprefix\n    if (recipient.startsWith('bot_')) {\n      // botprefix\n        return recipient.replace('bot_','');\n    }\n    // var text = message.text;\n    // winston.debug(\"text\", text);\n    \n    if ( message.request== null || message.request.participants == null) {\n        return null;\n    }\n\n    var participants = message.request.participants;\n    winston.debug(\"participants\", participants);\n \n    var botId = this.getBotFromParticipants(participants);\n    winston.debug(\"botId: \" + botId);\n \n   if (botId) {\n      return botId;\n   }else {\n       return null;\n   }\n \n}\n\n}\n\n var botFromParticipant = new BotFromParticipant();\n\n module.exports = botFromParticipant;\n "
  },
  {
    "path": "utils/cacheUtil.js",
    "content": "module.exports = {\n    defaultTTL: Number(process.env.CACHE_DEFAULT_TTL) || 300, //5 minutes   \n    queryTTL: Number(process.env.CACHE_QUERY_TTL) || 30,    \n    longTTL: Number(process.env.CACHE_LONG_TTL) || 3600,  //1 hour    \n  };\n  "
  },
  {
    "path": "utils/commons/extend-query.js",
    "content": "'use strict';\n\nconst generateKey = require('./generate-key');\n\nmodule.exports = function (mongoose, cache) {\n  const exec = mongoose.Query.prototype.exec;\n\n  mongoose.Query.prototype.exec = function (op, callback = function () {}) {\n    console.log(\"quiiiiiiiiiiiiiii exec\")\n    if (!this.hasOwnProperty('_ttl')) return exec.apply(this, arguments);\n\n    if (typeof op === 'function') {\n      callback = op;\n      op = null;\n    } else if (typeof op === 'string') {\n      this.op = op;\n    }\n\n    const key = this._key || this.getCacheKey();\n    const ttl = this._ttl;\n    const isCount = ['count', 'countDocuments', 'estimatedDocumentCount'].includes(this.op);\n    const isLean = this._mongooseOptions.lean;\n    const model = this.model.modelName;\n\n    return new Promise((resolve, reject) => {\n      cache.get(key, (err, cachedResults) => {\n        //eslint-disable-line handle-callback-err\n        if (cachedResults != null) {\n          console.log(\"quiiiiiiiiiiiiiii  cache\")\n\n          if (isCount) {\n            callback(null, cachedResults);\n            return resolve(cachedResults);\n          }\n\n          if (!isLean) {\n            const constructor = mongoose.model(model);\n            cachedResults = Array.isArray(cachedResults) ? cachedResults.map(hydrateModel(constructor)) : hydrateModel(constructor)(cachedResults);\n          }\n\n          callback(null, cachedResults);\n          return resolve(cachedResults);\n        }\n        console.log(\"quiiiiiiiiiiiiiii not cache\")\n\n        exec.call(this).then(results => {\n          cache.set(key, results, ttl, () => {\n            callback(null, results);\n            return resolve(results);\n          });\n        }).catch(err => {\n          callback(err);\n          reject(err);\n        });\n      });\n    });\n  };\n\n  mongoose.Query.prototype.cache = function (ttl = 60, customKey = '') {\n    if (typeof ttl === 'string') {\n      customKey = ttl;\n      ttl = 60;\n    }\n\n    this._ttl = ttl;\n    this._key = customKey;\n    return this;\n  };\n\n  mongoose.Query.prototype.getCacheKey = function () {\n    const key = {\n      model: this.model.modelName,\n      op: this.op,\n      skip: this.options.skip,\n      limit: this.options.limit,\n      sort: this.options.sort,\n      _options: this._mongooseOptions,\n      _conditions: this._conditions,\n      _fields: this._fields,\n      _path: this._path,\n      _distinct: this._distinct\n    };\n\n    return generateKey(key);\n  };\n};\n\nfunction hydrateModel(constructor) {\n  return data => {\n    return constructor.hydrate(data);\n  };\n}"
  },
  {
    "path": "utils/commons/q1.js",
    "content": "\nvar mongoose = require('mongoose');\n\nvar Request = require(\"./models/request\");\nvar User = require(\"./models/user\");\nvar faqkb = require(\"./models/faq_kb\");\nvar department = require(\"./models/department\");\nvar lead = require(\"./models/lead\");\n\nmongoose.connect(\"mongodb://localhost:27017/tiledesk\", { \"useNewUrlParser\": true, \"autoIndex\": false }, function(err) {\n  if (err) { return winston.error('Failed to connect to MongoDB on '+databaseUri);}\n});\n\n(async () => {\ntry {\nvar query = {  } ;\n    //console.log(\"start\",new Date())\n        var started = new Date();    \n\n    \nvar requests = await Request      \n    .find(query)    \n    // .populate('lead')\n    // .populate('department')\n    // .populate('participatingBots')\n    // .populate('participatingAgents')  \n    // .populate({path:'requester',populate:{path:'id_user'}})\n    .sort({updatedAt: 'desc'})\n    .limit(1).lean()\n   .exec();\n   //  .exec( function(err, requests) {\n   //     console.log(\"requests\",requests);\n   //  });   \n\n   if (requests && requests.length>0) {\n    requests.forEach(request => {\n    \n      if (request.agents && request.agents.length>0) {\n        var agentsnew = new Array;\n        request.agents.forEach(a => {\n          agentsnew.push({id_user: a.id_user})\n        });\n\n        console.log(\"agentsnew\",agentsnew);\n        request.agents = agentsnew;\n\n      }\n     \n    });\n  }\n\n\n   console.log(\"end requests\", JSON.stringify(requests),requests.length,started, new Date()); \n} catch (e) {\nconsole.log(e);\n  }\n \n})();\n\nfunction wait () {\n//   if (!EXITCONDITION)\n        setTimeout(wait, 1000);\n};\nwait();\n\n\n"
  },
  {
    "path": "utils/commons/testperformance.js",
    "content": "\nvar mongoose = require('mongoose');\n\nvar Request = require(\"./models/request\");\nvar User = require(\"./models/user\");\nvar faqkb = require(\"./models/faq_kb\");\nvar department = require(\"./models/department\");\nvar lead = require(\"./models/lead\");\n\nmongoose.connect(\"mongodb://localhost:27017/tiledesk\", { \"useNewUrlParser\": true, \"autoIndex\": false }, function(err) {\n  if (err) { return winston.error('Failed to connect to MongoDB on '+databaseUri);}\n});\n\n(async () => {\ntry {\nvar query = { } ;\n// var query = { id_project: '5e7fc0d5045a6a5021a7e4be','$or':[ { 'agents.id_user': '5e9eab435170bc3889c948ce' },{ participants: '5e9eab435170bc3889c948ce' } ] } ;\n    //console.log(\"start\",new Date())\n        var started = new Date();    \nvar rs = await Request      \n    .find(query)    \n    .populate('lead')\n    .populate('department')\n    .populate('participatingBots')\n    .populate('participatingAgents')  \n    .populate({path:'requester',populate:{path:'id_user'}})\n    .sort({updatedAt: 'desc'})\n    .limit(1000).lean()\n   .exec();\n   //  .exec( function(err, requests) {\n   //     console.log(\"requests\",requests);\n   //  });   \n   console.log(\"end rs\", rs,rs.length,started, new Date()); \n} catch (e) {\nconsole.log(e);\n  }\n \n})();\n\nfunction wait () {\n//   if (!EXITCONDITION)\n        setTimeout(wait, 1000);\n};\nwait();\n\n\n"
  },
  {
    "path": "utils/connectionUtil.js",
    "content": "\nclass ConnnectionUtil {\n\n    constructor(connection) {\n        this.connection = this.connection;\n    }\n\n    get() {\n        console.log(\"this.connection\",this.connection)\n        return this.connection;\n    }\n    set(connection) {\n        this.connection = connection;\n    }\n\n}\n\nvar connnectionUtil = new ConnnectionUtil();\n\nmodule.exports = connnectionUtil;\n\n\n"
  },
  {
    "path": "utils/datesUtil.js",
    "content": "const moment = require('moment-timezone');\n\nclass DatesUtil {\n\n  /**\n   * Helper function to parse a date with optional time\n   * Supports formats: DD/MM/YYYY, DD/MM/YYYY HH:mm, DD/MM/YYYY HH:mm:ss, DD/MM/YYYY, HH:mm:ss\n   * \n   * @param {string} dateString - Date string with or without time\n   * @param {string} timezone - Timezone\n   * @returns {moment.Moment} Parsed moment object\n   */\n  parseDate(dateString, timezone) {\n    if (!dateString || typeof dateString !== 'string') {\n      throw new Error(`The date must be a string: ${dateString}`);\n    }\n\n    if (!timezone || typeof timezone !== 'string') {\n      throw new Error(`The timezone must be a string: ${timezone}`);\n    }\n\n    // Validate timezone: check if it's a valid IANA timezone\n    if (!moment.tz.zone(timezone)) {\n      throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., \"Europe/Rome\", \"America/New_York\")`);\n    }\n\n    const trimmedDate = dateString.trim();\n\n    // List of possible formats to try\n    const formats = [\n      'DD/MM/YYYY HH:mm:ss',\n      'DD/MM/YYYY, HH:mm:ss',\n      'DD/MM/YYYY HH:mm',\n      'DD/MM/YYYY, HH:mm',\n      'DD/MM/YYYY',\n      'YYYY/MM/DD HH:mm:ss',\n      'YYYY/MM/DD, HH:mm:ss',\n      'YYYY/MM/DD HH:mm',\n      'YYYY/MM/DD, HH:mm',\n      'YYYY/MM/DD'\n    ];\n\n    // Try every format until one valid is found\n    for (const format of formats) {\n      // Parse the date in the specified format (without timezone)\n      //const parsed = moment(trimmedDate, format, true); // strict mode\n      const parsed = moment.tz(trimmedDate, format, timezone);\n      if (parsed && parsed.isValid && parsed.isValid()) {\n        // Apply the timezone to the parsed date\n        const dateWithTimezone = parsed.tz(timezone);\n        \n        // Verify that the timezone was applied correctly\n        if (!dateWithTimezone || !dateWithTimezone.isValid || !dateWithTimezone.isValid()) {\n          throw new Error(`Unable to apply timezone ${timezone} to date: ${dateString}`);\n        }\n        \n        return dateWithTimezone;\n      }\n    }\n\n    throw new Error(`Unable to parse the date: ${dateString} with timezone: ${timezone}`);\n  }\n\n  /**\n   * Checks if a date string contains time information\n   * \n   * @param {string} dateString - Date string\n   * @returns {boolean} True if contains time information\n   */\n  hasTimeComponent(dateString) {\n    // Checks if contains time information (HH:mm or HH:mm:ss)\n    return /[0-9]{1,2}:[0-9]{2}(:[0-9]{2})?/.test(dateString);\n  }\n\n  /**\n   * Converts local dates to UTC for database queries\n   * \n   * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. \"20/01/2026\" or \"20/01/2026 14:30:00\"). Optional.\n   * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss (e.g. \"20/01/2026\" or \"20/01/2026 23:59:59\"). Optional.\n   * @param {string} timezone - User timezone (e.g. \"Europe/Rome\", \"America/New_York\", \"Asia/Tokyo\")\n   * @returns {Object} Object with startDateUTC and/or endDateUTC in ISO string format for MongoDB\n   */\n  convertLocalDatesToUTC(startDate, endDate, timezone) {\n    if (!timezone || typeof timezone !== 'string') {\n      throw new Error(`The timezone must be a string: ${timezone}`);\n    }\n\n    // Validate timezone: check if it's a valid IANA timezone\n    if (!moment.tz.zone(timezone)) {\n      throw new Error(`Invalid timezone: ${timezone}. Please use a valid IANA timezone (e.g., \"Europe/Rome\", \"America/New_York\")`);\n    }\n\n    const result = {\n      startDateUTC: null,\n      endDateUTC: null,\n      startDate: null,\n      endDate: null\n    };\n\n    // Parse startDate if provided\n    if (startDate) {\n      const startMoment = this.parseDate(startDate, timezone);\n      // If startDate does not contain time, use the start of the day\n      // Otherwise use the specified time\n      const startParsed = this.hasTimeComponent(startDate)\n        ? startMoment\n        : startMoment.startOf('day');\n\n      result.startDateUTC = startParsed.utc().toISOString();\n      result.startDate = startParsed.utc().toDate();\n    }\n\n    // Parse endDate if provided\n    if (endDate) {\n      const endMoment = this.parseDate(endDate, timezone);\n      // If endDate does not contain time, use the start of the next day\n      // Otherwise use the specified time\n      const endParsed = this.hasTimeComponent(endDate)\n        ? endMoment\n        : endMoment.clone().add(1, 'day').startOf('day');\n\n      result.endDateUTC = endParsed.utc().toISOString();\n      result.endDate = endParsed.utc().toDate();\n    }\n\n    // At least one date must be provided\n    if (!startDate && !endDate) {\n      throw new Error('At least one of startDate or endDate must be provided');\n    }\n\n    return result;\n  }\n\n  /**\n   * Creates a MongoDB query object with the converted dates in UTC\n   * \n   * @param {string|null|undefined} startDate - Start date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (from this date onwards).\n   * @param {string|null|undefined} endDate - End date in the format DD/MM/YYYY or DD/MM/YYYY HH:mm:ss. Optional (until this date).\n   * @param {string} timezone - User timezone\n   * @param {string} fieldName - Field name in the database (default: \"createdAt\")\n   * @returns {Object} MongoDB query object with $gte (if startDate provided), $lt/$lte (if endDate provided), or both\n   */\n  createDateRangeQuery(startDate, endDate, timezone, fieldName = 'createdAt') {\n    const { startDate: start, endDate: end } = this.convertLocalDatesToUTC(startDate, endDate, timezone);\n\n    const query = {};\n    const dateQuery = {};\n\n    // Add start date condition (from this date onwards)\n    if (start) {\n      dateQuery.$gte = start;\n    }\n\n    // Add end date condition (until this date)\n    if (end) {\n      // If endDate contains time, use $lte to include up to that time\n      // Otherwise use $lt to exclude the start of the next day\n      const endOperator = endDate && this.hasTimeComponent(endDate) ? '$lte' : '$lt';\n      dateQuery[endOperator] = end;\n    }\n\n    // Only add the date query if at least one condition was set\n    if (Object.keys(dateQuery).length > 0) {\n      query[fieldName] = dateQuery;\n    }\n\n    return query;\n  }\n\n}\n\nconst datesUtil = new DatesUtil();\nmodule.exports = datesUtil;"
  },
  {
    "path": "utils/fileUtils.js",
    "content": "const axios = require(\"axios\").default;\n\nclass FileUtils {\n\n    async downloadFromUrl(url) {\n\n        return new Promise((resolve, reject) => {\n\n            axios({\n              url: url,\n              responseType: 'arraybuffer',\n              method: 'GET'\n            }).then((resbody) => {\n              resolve(resbody.data);\n            }).catch((err) => {\n              reject(err);\n            })\n      \n          })\n      \n    }\n}\n\nvar fileUtils = new FileUtils();\n\nmodule.exports = fileUtils;"
  },
  {
    "path": "utils/httpUtil.js",
    "content": "\n'use strict';\n\nvar request = require('retry-request', {\n  request: require('request')\n});\nconst axios = require(\"axios\").default;\n\nlet defaultHeaders = {\n  'Content-Type': 'application/json'\n}\n\n\nclass HttpUtil {\n\n  call(url, headers, json, method) {\n    return new Promise(function (resolve, reject) {\n      request({\n        url: url,\n        headers: headers,\n        json: json,\n        method: method\n      }, function (err, result, json) {\n        //console.log(\"SENT notify for bot with url \" + url +  \" with err \" + err);\n        if (err) {\n          //console.log(\"Error sending notify for bot with url \" + url + \" with err \" + err);\n          return reject(err);\n        }\n        return resolve(json);\n      });\n    });\n  }\n\n  async post(url, payload, customHeaders, auth) {\n\n    return new Promise((resolve, reject) => {\n\n      let headers = customHeaders ? customHeaders : defaultHeaders;\n      if (auth) headers.Authorization = auth;\n\n      axios({\n        url: url,\n        headers: {\n          ...headers\n        },\n        data: payload,\n        method: 'POST'\n      }).then((resbody) => {\n        resolve(resbody);\n      }).catch((err) => {\n        if (err.response) {\n          reject(err.response);\n        } else {\n          reject(err);\n        }\n      })\n\n    })\n  }\n}\n\n\nvar httpUtil = new HttpUtil();\n\n/*\nasync function ciao() {\n    var res = await httpUtil.call(\"https://webhook.site/bd710929-9b43-4065-88db-78ee17f84aec\", undefined, {c:1}, \"POST\")\n    console.log(\"res\", res);\n    \n}\nciao();\n*/\n\nmodule.exports = httpUtil;\n"
  },
  {
    "path": "utils/i8nUtil.js",
    "content": "\n\n'use strict';\n\n\nclass I8nUtil {\n\n    //unused\n    getMessage(key, lang, labelsObject) {\n        if (!lang) {\n            lang = \"EN\";\n        }\n\n        lang = lang.toUpperCase();\n        \n        var label = \"\";\n\n        try {\n         label = labelsObject[lang][key];\n        } catch(e) {\n            label = labelsObject[\"EN\"][key];\n        }\n        return label;\n     \n    }\n\n   \n\n\n      \n  \n}\n\n\nvar i8nUtil = new I8nUtil();\n\n\nmodule.exports = i8nUtil;"
  },
  {
    "path": "utils/jobs-worker-queue-manager/JobManagerV2.js",
    "content": "var QueueManager = require(\"./queueManagerClassV2\");\n\nclass JobManager {\n    constructor(queueUrl, options) {\n        this.queueUrl = queueUrl;\n        // this.queueManager = null;\n        this.debug = false;    \n        if (options && options.debug!=undefined) {\n            this.debug = options.debug;\n        }\n\n        this.info = true;\n        this.queueManager = new QueueManager(this.queueUrl, options);\n\n        this.sendingJobs = [];\n        this.queuePublisherConnected = false;\n        this.queueConsumerConnected = false;\n\n    }\n    connectAndStartPublisher(callback) {\n        var that = this;\n        if (this.info) {console.log(\"[JobWorker] JobManager publisher started\");}\n        \n        this.queueManager.connect(function(status, err) {\n\n            if (err) {\n                console.log(\"[JobWorker] - connectAndStartPublisher - connection error: \", err);\n                if (callback) {\n                    callback(null, err)\n                    return;\n                }\n            }\n            if (that.debug) {console.log(\"[JobWorker] Queue started\");}\n            that.queuePublisherConnected = true;\n\n            that.queueManager.startPublisher(function() {\n                if (that.debug) {console.log(\"[JobWorker] Queue that.sendingJobs.length\",that.sendingJobs.length);}\n                if (that.sendingJobs.length > 0) {\n                    for (var i = 0; i<that.sendingJobs.length; i++) {\n                        if (that.debug) {console.log(\"[JobWorker] Queue that.sendingJobs[i]\",that.sendingJobs[i]);}\n                        // that.queueManager.send(that.sendingJobs[i].toString(), \"functions\");\n                        that.queueManager.sendJson(that.sendingJobs[i], \"functions\");\n                        // that.sendingJobs[i]();\n                    }\n                    //that.sendingJob = [];\n                    that.sendingJobs.length = 0; //empty array that.sendingJobs\n                    if (that.debug) {console.log(\"[JobWorker] that.sendingJobs[i] cleared\",that.sendingJobs);}\n\n                } \n\n                if (callback) {\n                    callback(status, null);\n                    return;\n                }\n            });\n        });\n    }\n\n    diconnectPublisher(callback) {\n\n        if (this.info) {console.log(\"[JobWorker] JobManager publisher disconnected\");}\n\n        this.queueManager.close((err) => {\n            if (err) {\n                console.error(\"JobManager: error closing connection: \", err);\n            }\n            callback(err);\n        })\n        \n\n    }\n\n    connectAndStartWorker() {\n        var that = this;\n        if (this.info) {console.log(\"[JobWorker] JobManager worker started\");}\n        \n        this.queueManager.connect(function() {\n            if (that.debug) {console.log(\"[JobWorker] Queue started\");}\n            that.queueConsumerConnected = true;\n\n            that.queueManager.startWorker(function() {\n                if (that.debug) {console.log(\"[JobWorker] Queue that.queueManager.startWorker\");}               \n            });\n        });\n    }\n\n    publish(payload, callback) {\n        \n        var packet = {payload: payload}\n        // if (this.debug) {console.log(\"JobManager this.queueConnected\",this.queueStarted);\n        if (this.queuePublisherConnected == true) {\n\n            if (this.debug) {console.log(\"[JobWorker] JobManager  this.queuePublisherConnected == true\");}\n            this.queueManager.sendJson(packet, \"functions\", (err, ok) => {\n                if (err) {\n                    console.error(\"sendJson error: \", err);\n                } else {\n                    if (this.debug) { console.log(\"sendJson ok\"); };\n                }\n                callback(err, ok);\n            });\n\n            // this.queueManager.on(fn);\n        } \n        // else {\n        //     if (this.debug) {console.log(\"[JobWorker] JobManager  this.queuePublisherConnected == false\");}\n            \n        //     this.connectAndStartPublisher();\n        //     this.sendingJobs.push(packet);\n\n        // }\n       \n    }\n\n\n    //Deprecated\n    schedule(fn, payload) {\n        \n        var func = {function: fn.toString(), payload: payload}\n        // if (this.debug) {console.log(\"JobManager this.queueConnected\",this.queueStarted);\n        if (this.queuePublisherConnected == true) {\n            if (this.debug) {console.log(\"[JobWorker] JobManager  this.queuePublisherConnected == true\");}\n            this.queueManager.send(func, \"functions\");\n\n            // this.queueManager.on(fn);\n        } else {\n            if (this.debug) {console.log(\"[JobWorker] JobManager  this.queuePublisherConnected == false\");}\n            \n            this.connectAndStartPublisher();\n            this.sendingJobs.push(func);\n\n        }\n       \n    }\n\n    run(callback) {\n        // if (this.queueConsumerConnected == true) {\n        //     if (this.debug) {console.log(\"JobManager  this.queueConsumerConnected == true\");\n        //     this.queueManager.send(fn.toString(), \"functions\");\n\n        // } else {\n            if (this.debug) {console.log(\"[JobWorker] JobManager connectAndStartWorker\");}\n            this.connectAndStartWorker();\n\n            if (callback) {\n                if (this.debug) {console.log(\"[JobWorker] JobManager  callback\", callback);}\n                this.queueManager.on(callback);\n            }\n        // }\n    }\n}\n\n\n\nmodule.exports = JobManager;"
  },
  {
    "path": "utils/jobs-worker-queue-manager/queueManagerClassV2.js",
    "content": "var amqp = require('amqplib/callback_api');\n\nvar listeners = [];\n\n\nclass QueueManager {\n\nconstructor(url, options) {\n  this.debug = false;    \n  if (options && options.debug!=undefined) {\n      this.debug = options.debug;\n  }\n\n  this.pubChannel = null;\n  this.offlinePubQueue = [];\n\n  // if the connection is closed or fails to be established at all, we will reconnect\n  this.amqpConn = null;\n\n  this.url = url  || \"amqp://localhost\";\n  // process.env.CLOUDAMQP_URL + \"?heartbeat=60\"\n\n  // this.exchange = 'amq.topic';\n  this.exchange = 'tiledeskserver';\n  if (options && options.exchange!=undefined) {\n      this.exchange = options.exchange;\n  }\n  this.defaultTopic = \"subscription_run\";\n  if (options && options.defaultTopic!=undefined) {\n    this.defaultTopic = options.defaultTopic;\n  }\n\n  this.topic = \"jobsmanager\";\n  if (options && options.topic!=undefined) {\n    this.topic = options.topic;\n  }\n\n  // this.listeners = [];\n}\n\n\nconnect(callback) {\n  var that = this;\n  // console.log(\"[JobWorker] connect\", this.url);\n  // return new Promise(function (resolve, reject) {\n    amqp.connect(this.url, function(err, conn) {\n      if (err) {\n        // if (this.debug) {console.log(\"[AMQP]\", err.message);\n        console.error(\"[JobWorker] AMQP error\", err);\n        return setTimeout(function () {\n          if (that.debug) { console.log(\"[JobWorker] AMQP reconnecting\"); }\n          that.connect(callback)\n        }, 1000);\n\n      }\n      conn.on(\"error\", function(err) {\n        if (err.message !== \"Connection closing\") {\n          console.log(\"[JobWorker] AMQP conn error\", err);\n        }\n      });\n      conn.on(\"close\", function() {\n        // if (that.debug) {console.log(\"[JobWorker] AMQP reconnecting\");}\n        console.log(\"[JobWorker] AMQP reconnecting\")\n        return setTimeout(() => {\n          that.connect(callback);\n        }, 1000);\n      });\n\n      console.log(\"[JobWorker] AMQP connected\")\n      that.amqpConn = conn;\n\n      // that.whenConnected(callback);\n\n      if (callback) {\n        callback('Connected', null);\n      }\n    //   return resolve();\n    // });\n  });\n}\n\nclose(callback) {\n  if (this.debug) { console.log(\"Closing connection..\"); };\n  this.amqpConn.close((err) => {\n    callback(err);\n  });\n\n}\n\nwhenConnected(callback) {\n  var that = this;\n  // that.startPublisher(callback);\n  that.startPublisher();\n  that.startWorker(callback);\n}\n\n\nstartPublisher(callback) {\n  var that = this;\n  that.amqpConn.createConfirmChannel(function (err, ch) {\n    if (that.closeOnErr(err)) return;\n    ch.on(\"error\", function (err) {\n      if (that.debug) { console.log(\"[JobWorker] AMQP channel error\", err); }\n    });\n    ch.on(\"close\", function () {\n      if (that.debug) { console.log(\"[JobWorker] AMQP channel closed\"); }\n    });\n\n    // if (this.debug) {console.log(\"[AMQP] pubChannel\");\n    that.pubChannel = ch;\n    // console.log(\"[JobWorker] that.pubChannel\",that.pubChannel);\n    // while (true) {\n    //   var m = that.offlinePubQueue.shift();\n    //   if (!m) break;\n    //   that.publish(m[0], m[1], m[2]);\n    // }\n\n    if (callback) {\n      callback(err);\n    }\n\n  });\n}\n\n// method to publish a message, will queue messages internally if the connection is down and resend later\npublish(exchange, routingKey, content, callback) {\n  var that = this;\n  if (that.debug) {console.log(\"[JobWorker] that\", that);}\n  if (that.debug) {console.log(\"[JobWorker] that.pubChannel\", that.pubChannel);}\n  that.pubChannel.publish(exchange, routingKey, content, { persistent: false },\n    function(err, ok) {\n      callback(err, ok)\n    }\n  );\n\n  // try {\n  //   if (that.debug) {console.log(\"[JobWorker] that\", that);}\n  //   if (that.debug) {console.log(\"[JobWorker] that.pubChannel\", that.pubChannel);}\n  //   that.pubChannel.publish(exchange, routingKey, content, { persistent: false },\n  //                      function(err, ok) {\n  //                         callback(err, ok)\n  //                       //  if (err) {\n  //                       //   // if (that.debug) {console.log(\"[JobWorker] AMQP publish\", err);}\n  //                       //   // console.log(\"[JobWorker] AMQP publish\", err);\n  //                       //   // that.offlinePubQueue.push([exchange, routingKey, content]);\n  //                       //   // that.pubChannel.connection.close();\n  //                       //  }\n  //                       //  console.log(\"publish OK\")\n  //                       // //  if (that.debug) {console.log(\"[AMQP] publish sent\", content);}\n  //                      });\n  // } catch (e) {\n  //   console.log(\"[JobWorker] AMQP publish catch\", e);\n  //   if (this.debug) {console.log(\"[JobWorker] AMQP publish\", e);}\n  //   that.offlinePubQueue.push([exchange, routingKey, content]);\n  // }\n}\n\n// A worker that acks messages only if processed succesfully\n// var channel;\nstartWorker(callback) {\n  var that = this;\n\n    that.amqpConn.createChannel(function(err, ch) {\n      if (that.closeOnErr(err)) return;\n      ch.on(\"error\", function(err) {\n        if (this.debug) {console.log(\"[JobWorker] AMQP channel error\", err);}\n      });\n      ch.on(\"close\", function() {\n        if (this.debug) {console.log(\"[JobWorker] AMQP channel closed\");}\n      });\n      ch.prefetch(10);\n      ch.assertExchange(that.exchange, 'topic', {\n        durable: false\n      });\n\n      if (that.debug) {console.log(\"[JobWorker] AMQP that.topic\", that.topic);}\n      ch.assertQueue(that.topic, { durable: false }, function(err, _ok) {\n        if (that.closeOnErr(err)) return;\n        ch.bindQueue(_ok.queue, that.exchange, that.defaultTopic, {}, function(err3, oka) {\n          if (that.debug) {console.log(\"[JobWorker] Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: \" + that.defaultTopic );}\n            // if (this.debug) {console.log(\"Data queue\", oka)\n        });    \n        ch.bindQueue(_ok.queue, that.exchange, \"functions\", {}, function(err3, oka) {\n          if (that.debug) {console.log(\"[JobWorker] Queue bind: \"+_ok.queue+ \" err: \"+err3+ \" key: \" + \"functions\" );}\n          // if (this.debug) {console.log(\"Data queue\", oka)\n        });    \n\n        if (that.debug) {console.log(\"[JobWorker] AMQP that.topic\", that.topic);}\n        ch.consume(that.topic, that.processMsg2, { noAck: true });\n        // ch.consume(\"jobs\", that.processMsg, { noAck: false });\n        if (that.debug) {console.log(\"[JobWorker] Worker is started\");}\n\n        if (callback) {\n          callback();\n          if (that.debug) {console.log(\"[JobWorker] called callback worker\");}\n        }\n      });\n  \n\n    });\n}\n\n\n\n// work(msg, cb) {\n//   const message_string = msg.content.toString();\n//   const topic = msg.fields.routingKey //.replace(/[.]/g, '/');\n\n//   if (this.debug) {console.log(\"Got msg topic:\" + topic);\n\n//   if (this.debug) {console.log(\"Got msg:\"+ message_string +  \" topic:\" + topic);\n\n//   if (topic === 'subscription_run') {\n//     if (this.debug) {console.log(\"here topic:\" + topic);\n//     // requestEvent.emit('request.create.queue', msg.content);\n//     subscriptionEvent.emit('subscription.run.queue', JSON.parse(message_string));\n//   } \n//   cb(true);\n//   if (this.debug) {console.log(\"okookokkkkkkk msg:\");\n// }\n\nprocessMsg2(msg) {\n\n  // console.log(\"processMsg2:\", msg);\n\n  const message_string = msg.content.toString();\n  // console.log(\"processMsg2.1:\", msg);\n\n  const topic = msg.fields.routingKey //.replace(/[.]/g, '/');\n  // console.log(\"processMsg2.2:\", msg);\n\n  // if (this.debug) {console.log(\"Got msg topic:\" + topic);} //this is undefined in this method\n  // console.log(\"Got msg topic:\" + topic);\n\n  // if (this.debug) {console.log(\"Got msg1:\"+ message_string +  \" topic:\" + topic);}\n  // console.log(\"Got msg1:\"+ message_string +  \" topic:\" + topic);\n\n  if (topic === 'functions') {\n    // if (this.debug) {console.log(\"Got msg2:\"+ JSON.stringify(message_string) +  \" topic:\" + topic);}\n    // console.log(\"Got msg2:\"+ JSON.stringify(message_string) +  \" topic:\" + topic);\n\n    var fdata = JSON.parse(message_string)\n\n    // if (this.debug) {console.log(\"Got msg3:\"+ fdata.function +  \" fdata.function:\",  fdata.payload);}\n\n\n\n    /*\n\n    // var fields = Object.keys(fdata.payload).map((key) => [key, fdata.payload[key]]);\n    \n    // var fields = Object.keys(fdata.payload)\n\n    // if (this.debug) {console.log(\"Got fields:\"+ fields );\n\n    // eval(fdata.function)\n\n    */\n   \n    if (fdata.function) {\n      var fn = new Function(\"payload\", fdata.function);\n\n      // if (this.debug) {console.log(\"Got fn:\"+ fn);}\n\n    /*  \n      // var fn = new Function(fields, fdata.function);\n      \n      // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/Function\n      // var fn = new Function(\"name\",'if (this.debug) {console.log(\"ciao: \" + name);');\n      // fn(\"andrea\")\n\n      // var dataArray = Object.keys(fdata.payload).map(function(k){return fdata.payload[k]});\n      // if (this.debug) {console.log(\"Got dataArray:\", dataArray );\n\n      // fn(dataArray);\n    */\n\n\n      var ret = fn(fdata.payload)\n      // if (this.debug) {console.log(\"Got ret:\"+ ret);}\n      // console.log(\"Got ret:\"+ ret);\n\n    }\n\n    // else {\n    //   console.log(\"no function found\");\n    // }\n    \n\n  }\n\n  // if (topic === 'subscription_run') {\n  //   if (this.debug) {console.log(\"here topic:\" + topic);\n  //   // requestEvent.emit('request.create.queue', msg.content);\n  //   subscriptionEvent.emit('subscription.run.queue', JSON.parse(message_string));\n  // } \n  \n\n  // serve?\n  // if (this.debug) {console.log(\"listeners.length:\" + listeners.length);}\n\n  if (listeners && listeners.length>0) {\n    for( var i = 0; i< listeners.length; i++) {\n      // if (this.debug) {console.log(\"listeners[i]:\" + listeners[i]);}\n      listeners[i](fdata);\n    }\n  }\n\n  // if (this.debug) {console.log(\"listeners\", this.listeners);\n}\n// processMsg(msg) {\n//   if (this.debug) {console.log(\"processMsg msg:\", msg);\n\n//   var that = this;\n//   that.work(msg, function(ok) {\n//     try {\n//       if (ok)\n//         ch.ack(msg);\n//       else\n//         ch.reject(msg, true);\n//     } catch (e) {\n//       that.closeOnErr(e);\n//     }\n//   });\n// }\n\ncloseOnErr(err) {\n  if (!err) return false;\n  console.error(\"[JobWorker] AMQP error\", err);\n  this.amqpConn.close();\n  return true;\n}\n\nsendJson(data, topic, callback) {\n\n  this.publish(this.exchange, topic || this.defaultTopic, Buffer.from(JSON.stringify(data)), (err, ok) => {\n    if (callback) {\n      callback(err, ok);\n    }\n  });\n}\n\nsend(string, topic) {\n  if (this.debug) {console.log(\"[JobWorker] send(string): \", string);}\n  this.publish(this.exchange, topic || this.defaultTopic, Buffer.from(string));\n}\n\non(fn) {\n  listeners.push(fn);\n}\n\n\n\n}\n\n\n\n\nmodule.exports = QueueManager;\n"
  },
  {
    "path": "utils/orgUtil.js",
    "content": "\n\n'use strict';\nvar global = require('../config/global');\nvar email = require('../config/email');\nvar winston = require('../config/winston');\n\n\n\n\nclass OrgUtil {\n\n    constructor() {\n        this.ORGANIZATION_ENABLED = process.env.ORGANIZATION_ENABLED || global.organizationEnabled;\n        if (this.ORGANIZATION_ENABLED===true || this.ORGANIZATION_ENABLED === \"true\") {\n            this.ORGANIZATION_ENABLED = true;\n        }else {\n            this.ORGANIZATION_ENABLED = false;\n        }\n\n        winston.info(\"Organization enabled: \"+ this.ORGANIZATION_ENABLED );\n\n        this.ORGANIZATION_BASE_URL = process.env.ORGANIZATION_BASE_URL || global.organizationBaseUrl;\n        winston.info(\"Organization base url: \"+ this.ORGANIZATION_BASE_URL );\n\n    }\n\n    getOrg(req) {\n\n        // if (this.ORGANIZATION_ENABLED===false) {\n        //     return undefined;\n        // }\n\n\n        // let host = req.get('host');\n        // winston.info(\"host: \"+ host );\n\n        let origin = req.get('origin');\n        winston.debug(\"origin: \"+ origin );\n\n        // winston.info(\"email: \" + email.baseUrl);\n        winston.debug(\"this.ORGANIZATION_BASE_URL: \" + this.ORGANIZATION_BASE_URL); \n        // global.organizationBaseUrl\n        // if (host !=email.baseUrl ) {\n        if (origin && origin.indexOf(this.ORGANIZATION_BASE_URL)>-1) {\n        // if (origin!=this.ORGANIZATION_BASE_URL) {\n            // winston.info(\"host found: \"+ host );\n            // return host;\n            winston.info(\"origin found: \"+ origin );\n            return origin;\n        }\n        winston.debug(\"origin not found: \"+ origin );\n        \n        // winston.info(\"host not found: \"+ host );\n        // if (host.indexOf(\"localhost1\")>-1) {\n        //     console.log(\"host found: \"+ host );\n        //     return \"localhost\";\n        // }\n        return undefined;\n     \n    }\n\n   \n\n\n      \n  \n}\n\n\nvar orgUtil = new OrgUtil();\n\n\nmodule.exports = orgUtil;"
  },
  {
    "path": "utils/phoneUtil.js",
    "content": "'use strict';\n\n\nconst { parsePhoneNumberFromString } = require('libphonenumber-js');\n\nfunction normalizePhone(phone) {\n  if (phone == null) return null;\n\n  if (typeof phone !== 'string') {\n    phone = String(phone);\n  }\n\n  let trimmed = phone.trim();\n  if (!trimmed) return null;\n\n  // Convert 00 to +\n  if (trimmed.startsWith('00')) {\n    trimmed = '+' + trimmed.slice(2);\n  }\n\n  // Remove spaces and strange characters (keep + initial)\n  trimmed = trimmed.replace(/[^\\d+]/g, '');\n\n  // If it starts with +\n  if (trimmed.startsWith('+')) {\n    const parsed = parsePhoneNumberFromString(trimmed);\n\n    if (parsed && parsed.isValid()) {\n      return parsed.number; // Correct E.164\n    }\n\n    // + present but not valid → remove the +\n    return trimmed.replace(/^\\+/, '');\n  }\n\n  // Try as international without +\n  const parsedIntl = parsePhoneNumberFromString('+' + trimmed);\n\n  if (parsedIntl && parsedIntl.isValid()) {\n    return parsedIntl.number;\n  }\n\n  // Locale → return only digits\n  const digitsOnly = trimmed.replace(/\\D/g, '');\n  return digitsOnly || null;\n}\n\n\nmodule.exports = {\n    normalizePhone: normalizePhone\n};"
  },
  {
    "path": "utils/project_userUtil.js",
    "content": "\nclass ProjectUserUtil {\n\n    isBusy(project_user, project_max_assigned_chat) {\n       \n        // console.log(\"project\",project);\n        // var project_max_assigned_chat  = project && project.settings && project.settings.max_agent_assigned_chat;\n        // var project_max_assigned_chat  = 0;\n\n        var maxAssignedChat=undefined;\n        // console.log(\"project_max_assigned_chat:\", project_max_assigned_chat);\n        if (project_max_assigned_chat == undefined) {\n            return false;\n        }\n\n        if (project_max_assigned_chat!=undefined && project_max_assigned_chat> -1) {\n            maxAssignedChat = project_max_assigned_chat;\n        }\n\n        if (project_user.max_assigned_chat!=undefined && project_user.max_assigned_chat>-1 && project_user.max_assigned_chat > maxAssignedChat) {\n            maxAssignedChat = project_user.max_assigned_chat;\n        }\n\n        // console.log(\"maxAssignedChat: \"+maxAssignedChat);\n        // console.log(\"project_user.id: \"+project_user.id);\n        // console.log(\"project_user.number_assigned_requests: \"+project_user.number_assigned_requests);\n\n        if (maxAssignedChat!=undefined && project_user.number_assigned_requests >= maxAssignedChat) {\n            return true;\n        }else {\n            return false;\n        }\n    }\n\n}\n\n var projectUserUtil = new ProjectUserUtil();\n\n module.exports = projectUserUtil;\n "
  },
  {
    "path": "utils/promiseUtil.js",
    "content": "class PromiseUtil {\n\n  async doAllSequentually(promises) {\n    // return new Promise(async function (resolve, reject) {         \n    // console.log(\"promises\",promises);\n    var values = [];\n    for (let i=0; i < promises.length; i++) {\n        // console.log(\"promises[i]\",promises[i]);\n\n    //   const val = await \n      var value = await promises[i]\n    //   .then(function(value) {\n    //     console.log(\"done promises[i]\",value);\n    //     values.push(value);\n    //     console.log(\"values before\",values);\n    //   });\n        values.push(value);\n    };\n\n    //console.log(\"values\",values);\n    // return resolve(values);\n    \n    // });\n    return values;\n  }\n}\n\nvar promiseUtil = new PromiseUtil();\n\n\nmodule.exports = promiseUtil;"
  },
  {
    "path": "utils/recipientEmailUtil.js",
    "content": "var Project_user = require(\"../models/project_user\");\nvar Group = require(\"../models/group\");\nvar RoleConstants = require(\"../models/roleConstants\");\n\n\nclass RecipientEmailUtil {\n\n    iterateProjectUser(project_users) {\n        let arr = [];\n        if (project_users && project_users.length>0) {\n            project_users.forEach(project_user => {\n                if (project_user.id_user && project_user.id_user.email) {\n                    // tonew = tonew + project_user.id_user.email + \", \";\n                    arr.push(project_user.id_user.email)\n                } \n            });\n        }\n        return arr;\n    }\n\n    async process(to, projectid) {\n     \n      if (to.startsWith('@')) {     //mention\n        // console.log(\"startsWith\", to);\n\n        let project_users = await Project_user.find({ id_project: projectid,  role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN]}, status: \"active\"} ).populate('id_user').exec();\n\n        let tonewArr = this.iterateProjectUser(project_users);\n\n        let tonew = tonewArr.toString();\n        // console.log(\"tonew mention\", tonew);  \n        return tonew; \n                       \n\n\n      }\n      if (to.indexOf('@')==-1) {   //group\n        // console.log(\"indexOf-1\", to);\n        let tonewArr = []\n        let group = await Group.findOne({name: to, id_project: projectid}).exec();\n        if (group) {\n            // console.log(\"group\", group);  \n            let project_users = await Project_user.find({ id_project: projectid, id_user: { $in : group.members}, role: { $in : [RoleConstants.OWNER, RoleConstants.ADMIN, RoleConstants.SUPERVISOR, RoleConstants.AGENT]}, status: \"active\" }).populate('id_user').exec();       \n            // console.log(\"project_users\", project_users);  \n            tonewArr = this.iterateProjectUser(project_users);\n        }\n        let tonew = tonewArr.toString();\n        // console.log(\"tonew group\", tonew);  \n        return tonew; \n\n      }\n    //   console.log(\"to.indexOf('@')\",to.indexOf('@'));\n      if (to.indexOf('@')>0) {   //standard email\n        // console.log(\"indexOf>0\", to);\n        // console.log(\"tonew standard\", to);  \n         \n        return to;\n      }\n      \n    }\n  }\n  \n  var recipientEmailUtil = new RecipientEmailUtil();\n\n  \n  module.exports = recipientEmailUtil;  "
  },
  {
    "path": "utils/requestUtil.js",
    "content": "\nclass RequestUtil {\n\n    getToken(headers) {\n        if (headers && headers.authorization) {\n        var parted = headers.authorization.split(' ');\n        if (parted.length === 2) {\n            return parted[1];\n        } else {\n            return null;\n        }\n        } else {\n        return null;\n        }\n    }\n\n    getProjectIdFromRequestId(request_id) {\n        if (request_id && request_id.length>0) {\n            request_id = request_id.replace(\"support-group-\",\"\");\n            const firstChar = request_id.indexOf(\"-\");\n            if (firstChar && request_id.length>0) {\n                return request_id.substring(0,request_id.indexOf(\"-\"));\n            }else {\n                return null;\n            }\n            \n        } else {\n            return null;\n        }\n\n    }\n\n    arraysEqual (a, b) {\n        if (a === b) return true;\n        if (a == null || b == null) return false;\n        if (a.length != b.length) return false;\n      \n        // If you don't care about the order of the elements inside\n        // the array, you should sort both arrays here.\n        // Please note that calling sort on an array will modify that array.\n        // you might want to clone your array first.\n      \n        for (var i = 0; i < a.length; ++i) {\n          if (a[i] !== b[i]) return false;\n        }\n        return true;\n      }\n\n}\n\n var requestUtil = new RequestUtil();\n\n module.exports = requestUtil;\n "
  },
  {
    "path": "utils/segment2mongoConverter.js",
    "content": "\nclass Segment2MongoConverter {\n\n    convert(query, segment) {\n        //console.log(\"qui\", query);\n\n        let condition = query;\n        //let condition = {};\n\n        // if (segment.match == \"any\") {\n        //      // db.inventory.find( { $or: [ { quantity: { $lt: 20 } }, { price: 10 } ] } )\n        //     condition[\"$or\"] = [];\n        // }\n\n        if (segment.filters && segment.filters.length > 0) {\n            for(var i = 0; i < segment.filters.length; i++) {\n                let filter = segment.filters[i];\n                switch (filter.operator) {\n                    case '=':\n                      //console.log('Operator =');\n                      this.convertEqualsOperatorFilter(condition, filter);\n                      break;\n                    case '!=':\n                        //console.log('Operator !=');\n                        this.convertNotEqualsOperatorFilter(condition, filter);\n                        break;\n                    case '>':\n                        //console.log('Operator !=');\n                        this.convertGreaterThanOperatorFilter(condition, filter);\n                        break;\n                    case '>=':\n                        //console.log('Operator >=');\n                        this.convertGreaterThanOrEqualOperatorFilter(condition, filter);\n                        break;\n                    case 'starts with':\n                        //console.log('Operator >=');\n                        this.convertStarsWithOperatorFilter(condition, filter);\n                        break;\n                    case 'contains':\n                        //console.log('Operator >=');\n                        this.convertContainsOperatorFilter(condition, filter);\n                        break;\n                    case 'is null':\n                        //console.log('Operator >=');\n                        this.convertIsUndefinedOperatorFilter(condition, filter);\n                        break;\n                    case 'is not null':\n                        //console.log('Operator >=');\n                        this.convertExistsOperatorFilter(condition, filter);\n                        break;\n\n                    default:\n                        console.log('Operator default');\n                    } \n\n            }\n        }\n\n        //console.log(\"qui2\", query);\n       \n    }\n\n    convertEqualsOperatorFilter(query, filter) {\n        query[filter.field] = filter.value;\n    }\n\n    convertNotEqualsOperatorFilter(query, filter) {\n        query[filter.field] = {\"$ne\": filter.value};\n    }\n\n    convertGreaterThanOperatorFilter(query, filter) {\n        query[filter.field] = {\"$gt\": filter.value};\n    }\n    convertGreaterThanOrEqualOperatorFilter(query, filter) {\n        query[filter.field] = {\"$gte\": filter.value};\n    }\n\n    convertStartWithOperatorFilter(query, filter) {\n        query[filter.field] = {\"$regex\": \"/^\"+filter.value+\"/i\"};\n    }\n    convertStartWithOperatorFilter(query, filter) {\n        query[filter.field] = {\"$regex\": \"/^\"+filter.value+\"/i\"};\n    }\n    convertContainsOperatorFilter(query, filter) {\n        query[filter.field] = {\"$regex\": filter.value};\n    }\n    convertIsUndefinedOperatorFilter(query, filter) {\n        query[filter.field] = {\"$exists\": false};\n    }\n    convertExistsOperatorFilter(query, filter) {\n        query[filter.field] = {\"$exists\": true};\n    }\n}\n\n var segment2MongoConverter = new Segment2MongoConverter();\n\n module.exports = segment2MongoConverter;\n "
  },
  {
    "path": "utils/sendEmailUtil.js",
    "content": "var winston = require('../config/winston');\nconst emailService = require(\"../services/emailService\");\nconst Project = require(\"../models/project\");\nvar handlebars = require('handlebars');\n\nclass SendEmailUtil {\n    async sendEmailDirect(to, text, id_project, recipient, subject, message) {\n\n        let project = await Project.findById(id_project);\n        winston.debug(\"project\", project);\n\n        winston.debug(\"text: \" + text);\n        winston.debug(\"recipient:\"+ recipient);\n        winston.debug(\"to:\" + to);\n\n        var template = handlebars.compile(text);\n\n        var replacements = {        \n            message: message,           \n          };\n        \n        var finaltext = template(replacements);\n        winston.debug(\"finaltext:\" + finaltext);\n\n\n        // sendEmailDirect(to, text, project, request_id, subject, tokenQueryString, sourcePage, payload) {\n        emailService.sendEmailDirect(to, finaltext, project, recipient, subject, undefined, undefined, message);\n        \n    }\n}\n\nvar sendEmailUtil = new SendEmailUtil();\n\nmodule.exports = sendEmailUtil;"
  },
  {
    "path": "utils/sendMessageUtil.js",
    "content": "\nvar messageService = require('../services/messageService');\nvar Faq_kb = require('../models/faq_kb');\nvar MessageConstants = require('../models/messageConstants');\nvar winston = require('../config/winston');\nvar User = require('../models/user');\nvar cacheUtil = require('../utils/cacheUtil');\n\n\nclass SendMessageUtil {\n\nasync send(sender, senderFullname, recipient, text, id_project, createdBy, attributes) {\n    // async send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) {\n        winston.debug(\"here0\") \n      try {\n        // var senderFullname = \"\";\n\n\n        if (sender === \"system\") {\n            \n        } else if (sender.startsWith(\"bot_\")) {      // botprefix\n            var id = sender.replace(\"bot_\",\"\");     // botprefix\n            winston.debug(\"bot id: \"+id);\n            sender = id; //change sender removing bot_\n            var bot = await Faq_kb.findById(id)    //TODO add cache_bot_here non sembra scattare.. dove viene usato?\n                    //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, id_project+\":faq_kbs:id:\"+id)\n                    .exec();\n            winston.debug(\"bot\",bot);                 \n            senderFullname = bot.name;           \n        } else {\n            winston.debug(\"user id: \"+sender);\n            var user = await User.findById(sender)                                //TODO user_cache_here\n              //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+sender)     //user_cache\n              .exec()   \n            winston.debug(\"user\", user);        \n            senderFullname = user.fullName;\n        }\n        winston.debug(\"senderFullname: \"+senderFullname);\n          }catch(e) {\n            winston.error(\"errro getting fullname for SendMessageUtil\", e);\n          }\n\n        // send(sender, senderFullname, recipient, text, id_project, createdBy, attributes, type, metadata, language) {\n        return messageService.send(sender, senderFullname, recipient, text, id_project, createdBy, attributes);\n        // return messageService.send(sender, senderFullname, recipient, text, id_project, createdBy, MessageConstants.CHAT_MESSAGE_STATUS.SENDING, attributes, type, metadata, language);\n    \n   }\n\n\n}\n\n\n\nvar sendMessageUtil = new SendMessageUtil();\n\nmodule.exports = sendMessageUtil;\n\n\n\n\n\n\n// var mongoose = require('mongoose');\n// mongoose.connect(\"mongodb://localhost:27017/tiledesk\", { \"useNewUrlParser\": true, \"autoIndex\": false }, function(err) {\n//   if (err) { return winston.error('Failed to connect to MongoDB on '+databaseUri);}\n// });\n// sendMessageUtil.send(\"5e79e711ecb9230ac1f5b49f\",\"123\",\"ciao\",\"5ebac3685704c9377c359675\");\n// sendMessageUtil.send(\"bot_5ebac3685704c9377c35967a\",\"123\",\"ciao\",\"5ebac3685704c9377c359675\")\n// function wait () {\n//     //   if (!EXITCONDITION)\n//             setTimeout(wait, 1000);\n//     };\n//     wait();\n\n    "
  },
  {
    "path": "utils/stringUtil.js",
    "content": "\nclass StringUtil {\n\n    replaceAll(string, characterToReplace, replacement) {\n        return string.split(characterToReplace).join(replacement);\n    }\n}\n\n var stringUtil = new StringUtil();\n\n module.exports = stringUtil;\n "
  },
  {
    "path": "utils/userUtil.js",
    "content": "\nclass UserUtil {\n\n    decorateUser(user) {\n        user.id = user._id;\n        user.fullName = (user.firstname || '') + ' ' + (user.lastname || '');\n        return user;\n    }\n}\n\n var userUtil = new UserUtil();\n\n module.exports = userUtil;\n "
  },
  {
    "path": "utils/winston-mongodb/helpers.js",
    "content": "/**\n * @module helpers\n * @fileoverview Helpers for winston-mongodb\n * @license MIT\n * @author 0@39.yt (Yurij Mikhalevich)\n */\n'use strict';\nconst ObjectID = require('mongodb').ObjectID;\n\n\n/**\n * Prepares metadata to store into database.\n * @param {*} meta Metadata\n * @return {*}\n */\nexports.prepareMetaData = meta=>{\n  return cloneMeta(meta);\n};\n\n\n/**\n * Clones meta object and cleans it from circular references, replacing them\n * with string '[Circular]' and fixes field names to be storable within\n * MongoDB\n * @param {Object} node Current object or its leaf\n * @param {Array=} opt_parents Object's parents\n */\nfunction cloneMeta(node, opt_parents) {\n  if (!(node instanceof Object) || (node instanceof ObjectID)\n      || (node instanceof Buffer)) {\n    return node;\n  }\n  let copy = Array.isArray(node) ? [] : {};\n  if (node instanceof Date) {\n    return new Date(node.getTime());\n  } else if (node instanceof Error) {\n    // This is needed because Error's message, name and stack isn't accessible when cycling through properties\n    copy = {message: node.message, name: node.name, stack: node.stack};\n  }\n  opt_parents = opt_parents || [];\n  opt_parents.push(node);\n  for (let key in node) {\n    if (!Object.prototype.hasOwnProperty.call(node, key)) {\n      continue;\n    }\n    let value = node[key];\n    let newKey = key;\n    if (newKey.includes('.') || newKey.includes('$')) {\n        newKey = newKey.replace(/\\./g, '[dot]').replace(/\\$/g, '[$]');\n    }\n    if (value instanceof Object) {\n      if (opt_parents.indexOf(value) === -1) {\n        copy[newKey] = cloneMeta(value, opt_parents);\n      } else {\n        copy[newKey] = '[Circular]';\n      }\n    } else {\n      copy[newKey] = value;\n    }\n  }\n  opt_parents.pop();\n  return copy;\n}"
  },
  {
    "path": "utils/winston-mongodb/winston-mongodb.d.ts",
    "content": "// Type definitions for winston-mongodb\n// Project: https://github.com/winstonjs/winston-mongodb\n// Definitions by: miton18 <https://github.com/miton18>, blove <https://github.com/blove>,\n// Balazs Mocsai <https://github.com/mbale>\n\nimport { transports } from \"winston\";\nimport { WinstonMongoDBTransports } from 'winston-mongodb';\n\ndeclare module 'winston' {\n    /**\n     * Extending transport\n     *\n     * @export\n     * @interface Transports\n     * @extends {WinstonMongoDBTransports}\n     */\n    export interface Transports extends WinstonMongoDBTransports {}\n}\n\ndeclare module 'winston-mongodb' {\n    export interface MongoDBTransportInstance extends transports.StreamTransportInstance {\n        new (options?: MongoDBConnectionOptions) : MongoDBTransportInstance;\n        query: (callback: Function, options?: any) => Promise<any>;\n    }\n\n    export interface WinstonMongoDBTransports {\n        MongoDB: MongoDBTransportInstance;\n    }\n\n    /**\n     * Options for transport\n     *\n     * @export\n     * @interface MongoDBConnectionOptions\n     */\n    export interface MongoDBConnectionOptions {\n       /**\n        * Level of messages that this transport should log, defaults to 'info'.\n        *\n        * @type {string}\n        * @memberof MongoDBConnectionOptions\n        */\n       level?: string;\n       /**\n        * Boolean flag indicating whether to suppress output, defaults to false.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       silent?: boolean;\n       /**\n        * MongoDB connection uri, pre-connected db object or promise object which will be resolved with pre-connected db object.\n        *\n        * @type {(string | Promise<any>)}\n        * @memberof MongoDBConnectionOptions\n        */\n       db: string | Promise<any>;\n       /**\n        * MongoDB connection parameters (optional, defaults to {poolSize: 2, autoReconnect: true}).\n        *\n        * @type {*}\n        * @memberof MongoDBConnectionOptions\n        */\n       options?: any;\n       /**\n        * The name of the collection you want to store log messages in, defaults to 'log'.\n        *\n        * @type {string}\n        * @memberof MongoDBConnectionOptions\n        */\n       collection?: string;\n       /**\n        * Boolean indicating if you want to store machine hostname in logs entry, if set to true it populates MongoDB entry with 'hostname' field, which stores os.hostname() value.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       storeHost?: boolean;\n       /**\n        * Label stored with entry object if defined.\n        *\n        * @type {string}\n        * @memberof MongoDBConnectionOptions\n        */\n       label?: string;\n       /**\n        * Transport instance identifier. Useful if you need to create multiple MongoDB transports.\n        *\n        * @type {string}\n        * @memberof MongoDBConnectionOptions\n        */\n       name?: string;\n       /**\n        * In case this property is true, winston-mongodb will try to create new log collection as capped, defaults to false.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       capped?: boolean;\n       /**\n        * Size of logs capped collection in bytes, defaults to 10000000.\n        *\n        * @type {number}\n        * @memberof MongoDBConnectionOptions\n        */\n       cappedSize?: number;\n       /**\n        * Size of logs capped collection in number of documents.\n        *\n        * @type {number}\n        * @memberof MongoDBConnectionOptions\n        */\n       cappedMax?: number;\n       /**\n        * Will try to reconnect to the database in case of fail during initialization. Works only if db is a string. Defaults to false.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       tryReconnect?: boolean;\n       /**\n        * Will remove color attributes from the log entry message, defaults to false.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       decolorize?: boolean;\n       /**\n        *  Will leave MongoClient connected after transport shut down.\n        *\n        * @type {boolean}\n        * @memberof MongoDBConnectionOptions\n        */\n       leaveConnectionOpen?: boolean;\n       /**\n        * Configure which key is used to store metadata in the logged info object. Defaults to 'metadata' to remain compatible with the metadata format.\n        *\n        * @type {string}\n        * @memberof MongoDBConnectionOptions\n        */\n       metaKey?: string;\n       /**\n        * Seconds before the entry is removed. Works only if capped is not set.\n        *\n        * @type {number}\n        * @memberof MongoDBConnectionOptions\n        */\n       expireAfterSeconds?: number;\n    }\n    \n    const MongoDB: MongoDBTransportInstance;\n}"
  },
  {
    "path": "utils/winston-mongodb/winston-mongodb.js",
    "content": "/**\n * @module 'winston-mongodb'\n * @fileoverview Winston transport for logging into MongoDB\n * @license MIT\n * @author charlie@nodejitsu.com (Charlie Robbins)\n * @author 0@39.yt (Yurij Mikhalevich)\n */\n'use strict';\nconst util = require('util');\nconst os = require('os');\nconst mongodb = require('mongodb');\nconst winston = require('winston');\nconst Transport = require('winston-transport');\nconst Stream = require('stream').Stream;\nconst helpers = require('./helpers');\n\n\n\n/**\n * Constructor for the MongoDB transport object.\n * @constructor\n * @param {Object} options\n * @param {string=info} options.level Level of messages that this transport\n * should log.\n * @param {boolean=false} options.silent Boolean flag indicating whether to\n * suppress output.\n * @param {string|Object} options.db MongoDB connection uri or preconnected db\n * object.\n * @param {Object} options.options MongoDB connection parameters\n * (optional, defaults to `{poolSize: 2, autoReconnect: true, useNewUrlParser: true}`).\n * @param {string=logs} options.collection The name of the collection you want\n * to store log messages in.\n * @param {boolean=false} options.storeHost Boolean indicating if you want to\n * store machine hostname in logs entry, if set to true it populates MongoDB\n * entry with 'hostname' field, which stores os.hostname() value.\n * @param {string} options.label Label stored with entry object if defined.\n * @param {string} options.name Transport instance identifier. Useful if you\n * need to create multiple MongoDB transports.\n * @param {boolean=false} options.capped In case this property is true,\n * winston-mongodb will try to create new log collection as capped.\n * @param {number=10000000} options.cappedSize Size of logs capped collection\n * in bytes.\n * @param {number} options.cappedMax Size of logs capped collection in number\n * of documents.\n * @param {boolean=false} options.tryReconnect Will try to reconnect to the\n * database in case of fail during initialization. Works only if `db` is\n * a string.\n * @param {boolean=false} options.decolorize Will remove color attributes from\n * the log entry message.\n * @param {boolean=false} options.leaveConnectionOpen Will leave MongoClient connected\n * after transport shut down.\n * @param {number} options.expireAfterSeconds Seconds before the entry is removed.\n * Do not use if capped is set.\n */\nlet MongoDB = exports.MongoDB = function(options) {\n  Transport.call(this, options);\n  options = (options || {});\n  if (!options.db) {\n    throw new Error('You should provide db to log to.');\n  }\n  this.name = options.name || 'mongodb';\n  this.db = options.db;\n  this.options = options.options;\n  if (!this.options) {\n    this.options = {\n      poolSize: 2,\n      autoReconnect: true,\n      useNewUrlParser: true\n    };\n  }\n  this.collection = (options.collection || 'log');\n  this.level = (options.level || 'info');\n  this.labelRequired = (options.labelRequired || false);\n  this.silent = options.silent;\n  this.storeHost = options.storeHost;\n  this.label = options.label;\n  this.capped = options.capped;\n  this.cappedSize = (options.cappedSize || 10000000);\n  this.cappedMax = options.cappedMax;\n  this.decolorize = options.decolorize;\n  this.leaveConnectionOpen = options.leaveConnectionOpen;\n  this.expireAfterSeconds = !this.capped && options.expireAfterSeconds;\n  this.metaKey = options.metaKey || 'metadata';\n  if (this.storeHost) {\n    this.hostname = os.hostname();\n  }\n  this._opQueue = [];\n  let self = this;\n\n  function setupDatabaseAndEmptyQueue(db) {\n    return createCollection(db).then(db=>{\n      self.logDb = db;\n      processOpQueue();\n    }, err=>{\n      if (self.mongoClient && !self.leaveConnectionOpen) {\n        self.mongoClient.close();\n      }\n      console.error('winston-mongodb, initialization error: ', err);\n    });\n  }\n  function processOpQueue() {\n    self._opQueue.forEach(operation=>\n      self[operation.method].apply(self, operation.args));\n    delete self._opQueue;\n  }\n  function createCollection(db) {\n    let opts = Object.assign(\n      {},\n      self.capped ? {capped: true, size: self.cappedSize, max: self.cappedMax} : {}\n    );\n    return db.createCollection(self.collection, opts).then(col=>{\n      const ttlIndexName = 'timestamp_1';\n      let indexOpts = {name: ttlIndexName, background: true};\n      if (self.expireAfterSeconds) {\n        indexOpts.expireAfterSeconds = self.expireAfterSeconds;\n      }\n      return col.indexInformation({full: true}).then(info=>{\n        info = info.filter(i=>i.name === ttlIndexName);\n        if (info.length === 0) { // if its a new index then create it\n          return col.createIndex({timestamp: -1}, indexOpts);\n        } else { // if index existed with the same name check if expireAfterSeconds param has changed\n          if (info[0].expireAfterSeconds !== undefined &&\n              info[0].expireAfterSeconds !== self.expireAfterSeconds) {\n            return col.dropIndex(ttlIndexName)\n            .then(()=>col.createIndex({timestamp: -1}, indexOpts));\n          }\n        }\n      });\n    }).catch(err => {\n      // Since mongodb@3.6.0 db.createCollection throws an error (48) if the collection already exists\n      if (err.code !== 48) throw err;\n      return db.collection(self.collection);\n    }).then(()=>db);\n  }\n  function connectToDatabase(logger) {\n    return mongodb.MongoClient.connect(logger.db, logger.options\n    ).then(client=>{\n      logger.mongoClient = client;\n      setupDatabaseAndEmptyQueue(client.db());\n    }, err=>{\n      console.error('winston-mongodb: error initialising logger', err);\n      if (options.tryReconnect) {\n        console.log('winston-mongodb: will try reconnecting in 10 seconds');\n        return new Promise(resolve=>setTimeout(resolve, 10000)\n        ).then(()=>connectToDatabase(logger));\n      }\n    });\n  }\n\n  if ('string' === typeof this.db) {\n    connectToDatabase(this);\n  } else if ('function' === typeof this.db.then) {\n    this.db.then(client=>{\n      this.mongoClient = client;\n      setupDatabaseAndEmptyQueue(client.db());\n    }, err=>console.error('winston-mongodb: error initialising logger from promise', err));\n  } else if ('function' === typeof this.db.db) {\n    this.mongoClient = this.db;\n    setupDatabaseAndEmptyQueue(this.db.db())\n  } else { // preconnected object\n    console.warn(\n      'winston-mongodb: preconnected object support is deprecated and will be removed in v5');\n    setupDatabaseAndEmptyQueue(this.db);\n  }\n};\n\n\n/**\n * Inherit from `winston-transport`.\n */\nutil.inherits(MongoDB, Transport);\n\n\n/**\n * Define a getter so that `winston.transports.MongoDB`\n * is available and thus backwards compatible.\n */\nwinston.transports.MongoDB = MongoDB;\n\n\n/**\n * Closes MongoDB connection so using process would not hang up.\n * Used by winston Logger.close on transports.\n */\nMongoDB.prototype.close = function() {\n  this.logDb = null;\n  if (!this.mongoClient || this.leaveConnectionOpen) {\n    return;\n  }\n  this.mongoClient.close().then(()=>this.mongoClient = null).catch(err=>{\n    console.error('Winston MongoDB transport encountered on error during '\n        + 'closing.', err);\n  });\n};\n\n\n/**\n * Core logging method exposed to Winston. Metadata is optional.\n * @param {Object} info Logging metadata\n * @param {Function} cb Continuation to respond to when complete.\n */\nMongoDB.prototype.log = function(info, cb) {\n  if (!this.logDb) {\n    this._opQueue.push({method: 'log', args: arguments});\n    return true;\n  }\n  if (!cb) {\n    cb = ()=>{};\n  }\n  // Avoid reentrancy that can be not assumed by database code.\n  // If database logs, better not to call database itself in the same call.\n  process.nextTick(()=>{\n    if (this.silent) {\n      cb(null, true);\n    } \n    const decolorizeRegex = new RegExp(/\\u001b\\[[0-9]{1,2}m/g);\n    let entry = { timestamp: new Date(), level: (this.decolorize) ? info.level.replace(decolorizeRegex, '') : info.level };\n\n    // console.log(\"info\",info)\n    // console.log(\"info.label\",info.label)\n    let message = util.format(info.message, ...(info.splat || []));\n    entry.message = (this.decolorize) ? message.replace(decolorizeRegex, '') : message;\n    entry.meta = helpers.prepareMetaData(info[this.metaKey]);\n    if (this.storeHost) {\n      entry.hostname = this.hostname;\n    }\n    if (this.label) {\n      entry.label = this.label;\n    }\n    // console.log(\"info.labelRequired\",info.labelRequired)\n    if (this.labelRequired && !info.label) {\n      return cb(null, true); \n    }\n\n    if (info.label) {\n      entry.label = info.label; \n    }\n   \n    this.logDb.collection(this.collection).insertOne(entry).then(()=>{\n      this.emit('logged');\n      cb(null, true);\n    }).catch(err=>{\n      this.emit('error', err);\n      cb(err);\n    });\n  });\n  return true;\n};\n\n\n/**\n * Query the transport. Options object is optional.\n * @param {Object=} opt_options Loggly-like query options for this instance.\n * @param {Function} cb Continuation to respond to when complete.\n * @return {*}\n */\nMongoDB.prototype.query = function(opt_options, cb) {\n  if (!this.logDb) {\n    this._opQueue.push({method: 'query', args: arguments});\n    return;\n  }\n  if ('function' === typeof opt_options) {\n    cb = opt_options;\n    opt_options = {};\n  }\n  let options = this.normalizeQuery(opt_options);\n  let query = {timestamp: {$gte: options.from, $lte: options.until}};\n  let opt = {\n    skip: options.start,\n    limit: options.rows,\n    sort: {timestamp: options.order === 'desc' ? -1 : 1}\n  };\n  if (options.fields) {\n    opt.fields = options.fields;\n  }\n  this.logDb.collection(this.collection).find(query, opt).toArray().then(docs=>{\n    if (!options.includeIds) {\n      docs.forEach(log=>delete log._id);\n    }\n    cb(null, docs);\n  }).catch(cb);\n};\n\n\n/**\n * Returns a log stream for this transport. Options object is optional.\n * This will only work with a capped collection.\n * @param {Object} options Stream options for this instance.\n * @param {Stream} stream Pass in a pre-existing stream.\n * @return {Stream}\n */\nMongoDB.prototype.stream = function(options, stream) {\n  options = options || {};\n  stream = stream || new Stream;\n  let start = options.start;\n  if (!this.logDb) {\n    this._opQueue.push({method: 'stream', args: [options, stream]});\n    return stream;\n  }\n  stream.destroy = function() {\n    this.destroyed = true;\n  };\n  if (start === -1) {\n    start = null;\n  }\n  let col = this.logDb.collection(this.collection);\n  if (start != null) {\n    col.find({}, {skip: start}).toArray().then(docs=>{\n      docs.forEach(doc=>{\n        if (!options.includeIds) {\n          delete doc._id;\n        }\n        stream.emit('log', doc);\n      });\n      delete options.start;\n      this.stream(options, stream);\n    }).catch(err=>stream.emit('error', err));\n    return stream;\n  }\n  if (stream.destroyed) {\n    return stream;\n  }\n  col.isCapped().then(capped=>{\n    if (!capped) {\n      return this.streamPoll(options, stream);\n    }\n    let cursor = col.find({}, {tailable: true});\n    stream.destroy = function() {\n      this.destroyed = true;\n      cursor.destroy();\n    };\n    cursor.on('data', doc=>{\n      if (!options.includeIds) {\n        delete doc._id;\n      }\n      stream.emit('log', doc);\n    });\n    cursor.on('error', err=>stream.emit('error', err));\n  }).catch(err=>stream.emit('error', err));\n  return stream;\n};\n\n\n/**\n * Returns a log stream for this transport. Options object is optional.\n * @param {Object} options Stream options for this instance.\n * @param {Stream} stream Pass in a pre-existing stream.\n * @return {Stream}\n */\nMongoDB.prototype.streamPoll = function(options, stream) {\n  options = options || {};\n  stream = stream || new Stream;\n  let self = this;\n  let start = options.start;\n  let last;\n  if (!this.logDb) {\n    this._opQueue.push({method: 'streamPoll', args: [options, stream]});\n    return stream;\n  }\n  if (start === -1) {\n    start = null;\n  }\n  if (start == null) {\n    last = new Date(new Date - 1000);\n  }\n  stream.destroy = function() {\n    this.destroyed = true;\n  };\n  (function check() {\n    let query = last ? {timestamp: {$gte: last}} : {};\n    self.logDb.collection(self.collection).find(query).toArray().then(docs=>{\n      if (stream.destroyed) {\n        return;\n      }\n      if (!docs.length) {\n        return next();\n      }\n      if (start == null) {\n        docs.forEach(doc=>{\n          if (!options.includeIds) {\n            delete doc._id;\n          }\n          stream.emit('log', doc);\n        });\n      } else {\n        docs.forEach(doc=>{\n          if (!options.includeIds) {\n            delete doc._id;\n          }\n          if (!start) {\n            stream.emit('log', doc);\n          } else {\n            start -= 1;\n          }\n        });\n      }\n      last = new Date(docs.pop().timestamp);\n      next();\n    }).catch(err=>{\n      if (stream.destroyed) {\n        return;\n      }\n      next();\n      stream.emit('error', err);\n    });\n    function next() {\n      setTimeout(check, 2000);\n    }\n  })();\n  return stream;\n};"
  },
  {
    "path": "views/admin-get.jade",
    "content": "extends layout-admin\n\nblock content\n  h1= title\n  p Welcome to #{message}\n  form(name=\"add-estimation\", method=\"post\")\n        div.input\n            span.label FIREBASE_PRIVATE_KEY\n            input(type=\"text\", name=\"FIREBASE_PRIVATE_KEY\", value=\"#{FIREBASE_PRIVATE_KEY ? FIREBASE_PRIVATE_KEY : ''}\")\n        div.input\n            span.label FIREBASE_CLIENT_EMAIL\n            input(type=\"text\", name=\"FIREBASE_CLIENT_EMAIL\", value=\"#{FIREBASE_CLIENT_EMAIL ? FIREBASE_CLIENT_EMAIL : ''}\")\n        div.input\n            span.label FIREBASE_PROJECT_ID\n            input(type=\"text\", name=\"FIREBASE_PROJECT_ID\", value=\"#{FIREBASE_PROJECT_ID ? FIREBASE_PROJECT_ID : ''}\")\n        div.input\n            span.label FIREBASE_APIKEY\n            input(type=\"text\", name=\"FIREBASE_APIKEY\", value=\"#{FIREBASE_APIKEY}\")\n        div.input\n            span.label FIREBASE_AUTHDOMAIN\n            input(type=\"text\", name=\"FIREBASE_AUTHDOMAIN\", value=\"#{FIREBASE_AUTHDOMAIN}\")\n        div.input\n          span.label FIREBASE_DATABASEURL\n          input(type=\"text\", name=\"FIREBASE_DATABASEURL\", value=\"#{FIREBASE_DATABASEURL}\")\n        div.input\n          span.label FIREBASE_STORAGEBUCKET\n          input(type=\"text\", name=\"FIREBASE_STORAGEBUCKET\", value=\"#{FIREBASE_STORAGEBUCKET}\")          \n        div.input\n          span.label FIREBASE_MESSAGINGSENDERID\n          input(type=\"text\", name=\"FIREBASE_MESSAGINGSENDERID\", value=\"#{FIREBASE_MESSAGINGSENDERID}\")\n        div.input\n          span.label CHAT21_ENABLED\n          input(type=\"text\", name=\"CHAT21_ENABLED\", value=\"#{CHAT21_ENABLED}\")\n        div.input\n          span.label CHAT21_URL\n          input(type=\"text\", name=\"CHAT21_URL\", value=\"#{CHAT21_URL}\")\n        div.input\n          span.label CHAT21_APPID\n          input(type=\"text\", name=\"CHAT21_APPID\", value=\"#{CHAT21_APPID}\")\n        div.input\n          span.label CHAT21_ADMIN_TOKEN\n          input(type=\"text\", name=\"CHAT21_ADMIN_TOKEN\", value=\"#{CHAT21_ADMIN_TOKEN}\")         \n\n        div.actions\n            input(type=\"submit\", value=\"Save\")\n"
  },
  {
    "path": "views/admin-saved-get.jade",
    "content": "extends layout-admin\n\n\nblock content\n  h1= title\n  p Welcome to #{message}\n  a(href=\"/admin/\")\n   button(type=\"button\") Return to admin Home\n"
  },
  {
    "path": "views/error.jade",
    "content": "extends layout\n\nblock content\n  h1= message\n  h2= error.status\n  pre #{error.stack}\n"
  },
  {
    "path": "views/index.jade",
    "content": "extends layout\n\nblock content\n  h1= title\n  p Welcome to #{title}\n"
  },
  {
    "path": "views/layout-admin.jade",
    "content": "doctype html\nhtml\n  head\n    style(type=\"text/css\").\n      body{\n        background-color: #eeeeee;\n      }\n      input {\n        width: 100%;\n      }     \n    \n      img{\n        width: 200px;\n      }     \n      a{\n        color:#00B7FF\n      }\n    title= \"tiledesk chat transcript\"\n    link(rel='stylesheet', href='/stylesheets/style.css')\n  body\n    block content\n"
  },
  {
    "path": "views/layout.jade",
    "content": "doctype html\nhtml\n  head\n    style(type=\"text/css\").\n    \n      body{\n        background-color: #eeeeee;\n      }\n      .transcript-container {\n        padding: 25px 25px 5px;\n      }\n      input {\n        width: 80%;\n      }\n      img{\n        width: 200px;\n      }\n      .transcript-wrapper{\n        background-color: #ffffff;\n        padding: 15px 15px 25px;\n        border: 1px solid #e1eef4;\n        border-radius: 5px;\n      }\n      .transcript-title {\n        font: 28px \"Lucida Grande\", Helvetica, Arial, sans-serif;\n        font-weight: bold;\n        color: #ee7467;\n        text-align: center;\n      }\n      .transcript-p {\n        font: 14px \"Lucida Grande\", Helvetica, Arial, sans-serif;\n        background-color: #ffffff;\n        padding: 10px;\n        border-bottom: 1px solid #e1eef4;\n        margin-top:0;\n        margin-bottom:0px;\n        \n      }\n      .transcript-chat-date{\n        color:#657786;\n        padding-right: 15px;\n        font-size:13px;\n        }\n      .transcript-chat-sender{\n        color:#7695a5;\n        font-weight: 600;\n      }\n      .transcript-chat-msg{\n        color:#7695a5;\n        font-weight: 400;\n      }\n      .transcript-powered-by{\n        font: 12px \"Lucida Grande\", Helvetica, Arial, sans-serif;\n        color:#7695a5;\n        font-weight: 600;\n        text-align: right;\n      }\n      a{\n        color:#00B7FF\n      }\n\n    title= (brandName ? brandName + \" Chat Transcript\" : (title ? title + \" Chat Transcript\" : \"Chat Transcript\"))\n    //- link(rel='stylesheet', href='/stylesheets/style.css')\n  body\n    block content\n"
  },
  {
    "path": "views/messages.jade",
    "content": "extends layout\n\nblock content\n  div(class=\"transcript-container\")\n\n    if brandLogo\n      img.className#IdName(src=brandLogo alt=\"brand logo\")\n    else if !brandLogo && !brandName\n      img.className#IdName(src=\"https://tiledesk.com/wp-content/uploads/2022/07/tiledesk_v2.png\" alt=\"tiledesk logo\") \n      \n    div(class=\"transcript-wrapper\")\n      h1(class=\"transcript-title\") Chat Transcript\n\n\n      each message in messages\n        p(class=\"transcript-p\") \n          span(class=\"transcript-chat-date\") [#{message.createdAt.toLocaleString('en', { timeZone: 'UTC' })}] \n          span(class=\"transcript-chat-sender\") #{message.senderFullname}: \n          span(class=\"transcript-chat-msg\") #{message.text}\n    \n    if brandName\n      p(class=\"transcript-powered-by\") Powered by #{brandName}\n    else\n      p(class=\"transcript-powered-by\") Powered by  \n        a(href=\"https://tiledesk.com/\", target=\"_blank\") Tiledesk\n\n\n"
  },
  {
    "path": "views/messages_old.jade",
    "content": "extends layout\n\nblock content\n  div(class=\"transcript-container\")\n    img.className#IdName(src=\"https://tiledesk.com/wp-content/uploads/2022/07/tiledesk_v2.png\" alt=\"tiledesk logo\") \n      \n    div(class=\"transcript-wrapper\")\n      h1(class=\"transcript-title\") Chat Transcript\n\n\n      each message in messages\n        p(class=\"transcript-p\") \n          span(class=\"transcript-chat-date\") [#{message.createdAt.toLocaleString('en', { timeZone: 'UTC' })}] \n          span(class=\"transcript-chat-sender\") #{message.senderFullname}: \n          span(class=\"transcript-chat-msg\") #{message.text}\n      \n    p(class=\"transcript-powered-by\")  Powered by   \n      a(href=\"https://tiledesk.com/\", target=\"_blank\") tiledesk\n\n\n"
  },
  {
    "path": "websocket/pubsub.js",
    "content": "const  Map  = require('immutable').Map;\nvar _ = require('lodash');\n//const util = require('util')\n\n// import _ from 'lodash'\n// import uuid from 'uuid/v1'\nconst uuidv4 = require('uuid/v4');\n\nconst Subscription = require('./subscription');\n// winston.debug(\"Subscription\", Subscription);\nvar winston = require('../config/winston');\n\n\n\n\n// https://github.com/tabvn/pubsub/blob/master/server/src/pubsub.js\nclass PubSub {\n\n    constructor (wss, callbacksArg) {\n    winston.debug(\"wss\", wss);\n\n    this.wss = wss\n\n    this.clients = new Map()\n     this.subscription = new Subscription();\n\n    this.load = this.load.bind(this)\n    this.handleReceivedClientMessage = this.handleReceivedClientMessage.bind(\n      this)\n    this.handleAddSubscription = this.handleAddSubscription.bind(this)\n    this.handleUnsubscribe = this.handleUnsubscribe.bind(this)\n    this.handlePublishMessage = this.handlePublishMessage.bind(this)\n    this.removeClient = this.removeClient.bind(this)\n     \n     this.callbacks = callbacksArg;\n\n    this.load()\n  }\n\n   load() {\n\n    const wss = this.wss\n\n    wss.on('connection', async (ws,req) => {\n\n      ws.isAlive = true;\n\n      const id = this.autoId()\n\n      winston.debug('connection', id)\n      const client = {\n        id: id,\n        ws: ws,\n        userId: null,\n        subscriptions: [],\n      }\n     \n      if (this.callbacks && this.callbacks.onConnect) {\n        try {\n          var resCallBack =  await await this.callbacks.onConnect(client, req);\n          winston.debug(\"resCallBack onConnect\",resCallBack);\n        } catch(e) {\n          winston.warn(\"resCallBack onConnect err\",e);\n          return 0;\n        }        \n      }\n\n      // add new client to the map\n      this.addClient(client)\n\n     \n\n      // listen when receive message from client\n      ws.on('message',\n        (message) => this.handleReceivedClientMessage(id, message, req))\n\n      ws.on('close', async () => {\n        winston.debug('Client is disconnected')\n\n        clearInterval(ws.timer);\n        \n        // Find user subscriptions and remove\n        const userSubscriptions = this.subscription.getSubscriptions(\n          (sub) => sub.clientId === id)\n\n       \n        if (this.callbacks && this.callbacks.onDisconnect) {          \n          try {\n            var resCallBack =  await this.callbacks.onDisconnect(id, userSubscriptions);\n            winston.debug(\"resCallBack onDisconnect\",resCallBack);\n          } catch(e) {\n            winston.warn(\"resCallBack onDisconnect err\",e);\n            return 0;\n          }\n\n        }\n    \n        userSubscriptions.forEach((sub) => {\n          this.subscription.remove(sub.id)\n        })\n\n        // now let remove client\n\n        this.removeClient(id)\n\n      })\n      //https://stackoverflow.com/questions/46755493/websocket-ping-with-node-js\n     // ws.on('pong',function(mess) { winston.debug(ws.id+' receive a pong : '+mess); });\n\n      var thatThis = this;\n      //winston.debug(\"heartbeat timer\");\n      ws.timer=setInterval(function(){thatThis.ping(ws);},30000);\n\n    })\n\n  }\n\n  ping(ws) {\n    winston.debug('send a ping');\n    if (ws.isAlive === false) {\n      winston.debug('ws.isAlive is false terminating ws');\n      return ws.terminate();\n    }\n    //ws.ping('coucou',{},true);\n    ws.isAlive = false;\n    // {\n    //   action: 'publish',\n    //   payload: {\n    //     topic: topic,\n    //     method: method,\n    //     message: message,\n    //   },\n    // })\n    var message = {action: \"heartbeat\", payload: {message: {text: \"ping\"}}};\n    ws.send(JSON.stringify(message));\n  }\n  \n  /**\n   * Handle add subscription\n   * @param topic\n   * @param clientId = subscriber\n   */\n  handleAddSubscription (topic, clientId) {\n\n    const client = this.getClient(clientId)\n    if (client) {\n      const subscriptionId = this.subscription.add(topic, clientId)\n      winston.debug('handleAddSubscription this.subscription',JSON.stringify(this.subscription));\n      \n      client.subscriptions.push(subscriptionId)      \n      this.addClient(client)\n\n\n      // const client = {\n      //   id: id,\n      //   ws: ws,\n      //   userId: null,\n      //   subscriptions: [],\n      // }\n     \n     \n      //winston.debug('handleAddSubscription this.addClient',JSON.stringify(this.clients));\n    }\n\n  }\n\n  /**\n   * Handle unsubscribe topic\n   * @param topic\n   * @param clientId\n   */\n  handleUnsubscribe (topic, clientId) {\n\n    const client = this.getClient(clientId)\n\n    winston.debug('handleUnsubscribe client.id', client.id, \"subscriptions\", client.subscriptions);\n\n\n    let clientSubscriptions = _.get(client, 'subscriptions', [])\n    //winston.debug('handleUnsubscribe clientSubscriptions',clientSubscriptions);\n\n    const userSubscriptions = this.subscription.getSubscriptions(\n      (s) => s.clientId === clientId && s.type === 'ws')\n\n    winston.debug('handleUnsubscribe userSubscriptions',JSON.stringify(userSubscriptions));\n    //winston.debug(util.inspect(userSubscriptions, {showHidden: false, depth: null}))\n\n    userSubscriptions.forEach((sub) => {\n      //clientSubscriptions = clientSubscriptions.filter((id) => id !== sub.id);\n      //winston.debug('handleUnsubscribe clientSubscriptions',clientSubscriptions);\n      // now let remove subscriptions\n      winston.debug(\"handleUnsubscribe  sub.topic\",sub.topic);\n      winston.debug(\"handleUnsubscribe  topic\",topic);\n\n      if (sub.topic == topic) {\n        winston.debug(\"handleUnsubscribe  remove\",sub.id);\n        var index = clientSubscriptions.indexOf(sub.id);\n        winston.debug(\"handleUnsubscribe  index\",index);\n        if (index > -1) {\n          clientSubscriptions.splice(index, 1);\n        }\n        //clientSubscriptions = clientSubscriptions.remove(sub.id);\n\n        this.subscription.remove(sub.id)\n      }\n      \n    })\n\n    // userSubscriptions.forEach((sub) => {\n    //   clientSubscriptions = clientSubscriptions.filter((id) => id !== sub.id);\n    //   winston.debug('handleUnsubscribe clientSubscriptions',clientSubscriptions);\n    //   // now let remove subscriptions\n    //   this.subscription.remove(sub.id)\n    // })\n    winston.debug('handleUnsubscribe clientSubscriptions',clientSubscriptions);\n     winston.debug('handleUnsubscribe this.subscription', JSON.stringify(this.subscription));\n\n    // let update client subscriptions\n    if (client) {\n      client.subscriptions = clientSubscriptions\n      this.addClient(client)\n    }\n\n  }\n\n  /**\n   * Handle publish a message to a topic\n   * @param topic\n   * @param message\n   * @param from\n   * @isBroadcast = false that mean send all, if true, send all not me\n   */\n  handlePublishMessage (topic, message, from, isBroadcast = false, method) {\n  \n\n    let subscriptions = isBroadcast\n      ? this.subscription.getSubscriptions(\n        (sub) => sub.topic === topic && sub.clientId !== from)\n      : this.subscription.getSubscriptions(\n        (subs) => subs.topic === topic)\n\n\n        winston.debug(\"handlePublishMessage!!!!!!!!!!!\", subscriptions);    \n    // now let send to all subscribers in the topic with exactly message from publisher\n    subscriptions.forEach((subscription) => {\n\n      const clientId = subscription.clientId\n      const subscriptionType = subscription.type  // email, phone, ....\n      // winston.debug('CLient id of subscription', clientId, subscription)\n      // we are only handle send via websocket\n      if (subscriptionType === 'ws') {\n        this.send(clientId, {\n          action: 'publish',\n          payload: {\n            topic: topic,\n            method: method,\n            message: message,\n          },\n        })\n      }\n\n    })\n  }\n\n\n\n\n  /**\n   * Handle publish a message to a topic\n   * @param topic\n   * @param message\n   * @param from\n   * @isBroadcast = false that mean send all, if true, send all not me\n   */\n  handlePublishMessageToClientId (topic, message, clientId, method) {\n  \n        this.send(clientId, {\n          action: 'publish',\n          payload: {\n            topic: topic,\n            method: method,\n            message: message,\n          },\n        })\n      \n\n  }\n\n  /**\n   * Handle receive client message\n   * @param clientId\n   * @param message\n   */\n  async handleReceivedClientMessage (clientId, message, req) {\n/*\n    if (this.onMessageCallback ) {\n      this.onMessageCallback(clientId, message);\n    }*/\n    if (this.callbacks && this.callbacks.onMessage) {      \n      try {\n        var resCallBack =  await this.callbacks.onMessage(clientId, message);\n        winston.debug(\"resCallBack onMessage\",resCallBack);\n      } catch(e) {\n        winston.warn(\"resCallBack onMessage err\",e);\n        return 0;\n      }\n    }\n\n    const client = this.getClient(clientId)\n    // winston.debug('clientId',clientId, message);\n\n      //heartbeat\n      const ws = client.ws\n      ws.isAlive = true;\n   \n\n\n    if (typeof message === 'string') {\n\n      message = this.stringToJson(message)\n\n      const action = _.get(message, 'action', '')\n      switch (action) {\n\n        case 'me':\n\n          //Client is asking for his info\n\n          this.send(clientId,\n            {action: 'me', payload: {id: clientId, userId: client.userId}})\n\n          break\n\n        case 'heartbeat':\n          \n          const text = _.get(message, 'payload.message.text', null);\n          winston.debug('received heartbeat with text ',text);\n          if (text=='ping') {\n            var messageToSend = {action: 'heartbeat', payload: {message: {text: 'pong'}}};\n            // rispondi pong solo su ping e non su pong\n            winston.debug('received heartbeat from ',clientId,\" i send a  message: \",  messageToSend);         \n            this.send(clientId, messageToSend)\n                \n            \n          }\n            \n\n        break\n\n        case 'subscribe':\n\n          //@todo handle add this subscriber\n          const topic = _.get(message, 'payload.topic', null)\n          if (topic) {\n\n            if (this.callbacks && this.callbacks.onSubscribe) {\n              try {\n                var resCallBack =  await this.callbacks.onSubscribe(topic, clientId, req);\n                winston.debug(\"resCallBack onSubscribe\",resCallBack);\n                //console.log(\"resCallBack onSubscribe\",resCallBack);\n              } catch(e) {\n                winston.verbose(\"resCallBack onSubscribe err\",e);\n                return 0;\n              }\n            }\n\n            this.handleAddSubscription(topic, clientId);\n\n\n            if (resCallBack.publishFunction) {\n              resCallBack.publishFunction();\n            }\n\n            \n\n            // if (resCallBack.publishPromise) {\n            //   resCallBack.publishPromise.then(function(resultPublish){\n            //     // winston.info(\"resCallBack resultPublish\",resultPublish);\n            //     console.log(\"resCallBack resultPublish\",resultPublish);\n            //     // resultPublish.then(function(resultPublish2){\n            //     //   winston.info(\"resCallBack resultPublish2\",resultPublish2);\n            //     //   console.log(\"resCallBack resultPublish2\",resultPublish2);\n            //     // });\n\n            //   });\n            // }\n           \n\n\n          }\n\n          break\n\n        case 'unsubscribe':\n\n          const unsubscribeTopic = _.get(message, 'payload.topic')\n\n         \n\n          if (unsubscribeTopic) {\n\n            if (this.callbacks && this.callbacks.onUnsubscribe) {              \n              try {\n                var resCallBack =  await this.callbacks.onUnsubscribe(unsubscribeTopic, clientId, req);\n                winston.debug(\"resCallBack onUnsubscribe\",resCallBack);\n              } catch(e) {\n                winston.warn(\"resCallBack onUnsubscribe err\",e);\n                return 0;\n              }\n            }\n\n            this.handleUnsubscribe(unsubscribeTopic, clientId)\n          }\n\n          break\n\n        case 'publish':\n\n          const publishTopic = _.get(message, 'payload.topic', null)\n          const publishMessage = _.get(message, 'payload.message')\n          if (publishTopic) {\n            const from = clientId;\n\n            if (this.callbacks && this.callbacks.onPublish) {              \n              try {\n                var resCallBack =  await this.callbacks.onPublish(publishTopic, publishMessage, from, req);\n                winston.debug(\"resCallBack onPublish\",resCallBack);\n              } catch(e) {\n                winston.warn(\"resCallBack onPublish err\",e);\n                return 0;\n              }\n            }\n\n            this.handlePublishMessage(publishTopic, publishMessage, from)\n          }\n\n          break\n\n        case 'broadcast':\n\n          const broadcastTopicName = _.get(message, 'payload.topic', null)\n          const broadcastMessage = _.get(message, 'payload.message')\n          if (broadcastTopicName) {\n            if (this.callbacks && this.callbacks.onBroadcast) {              \n              try {\n                var resCallBack =  await this.callbacks.onBroadcast(broadcastTopicName, broadcastMessage, clientId, req);\n                winston.debug(\"resCallBack onPublish\",resCallBack);\n              } catch(e) {\n                winston.warn(\"resCallBack onPublish err\",e);\n                return 0;\n              }\n            }\n\n            this.handlePublishMessage(broadcastTopicName, broadcastMessage,\n              clientId, true)\n          }\n\n          break\n\n        default:\n\n          break\n      }\n\n    } else {\n      // maybe data message we handle later.\n    }\n\n  }\n\n  /**\n   * Convert string of message to JSON\n   * @param message\n   * @returns {*}\n   */\n  stringToJson (message) {\n\n    try {\n      message = JSON.parse(message)\n    } catch (e) {\n      winston.debug(e, message)\n    }\n\n    return message\n  }\n\n  /**\n   * Add new client connection to the map\n   * @param client\n   */\n  addClient (client) {\n\n    if (!client.id) {\n      client.id = this.autoId()\n    }\n    winston.debug('client added', {id: client.id, subscriptions: client.subscriptions});\n    this.clients = this.clients.set(client.id, client)\n     winston.debug('clients added: ',this.clients)\n  }\n\n  /**\n   * Remove a client after disconnecting\n   * @param id\n   */\n  removeClient (id) {\n    this.clients = this.clients.remove(id)\n  }\n\n  /**\n   * Get a client connection\n   * @param id\n   * @returns {V | undefined}\n   */\n  getClient (id) {\n\n    return this.clients.get(id)\n  }\n\n  /**\n   * Generate an ID\n   * @returns {*}\n   */\n  autoId () {\n    return uuidv4()\n  }\n\n  /**\n   * Send to client message\n   * @param message\n   */\n  send (clientId, message) {\n\n    const client = this.getClient(clientId)\n    if (!client) {\n      return\n    }\n    const ws = client.ws\n    try {\n      message = JSON.stringify(message)\n    }\n    catch (err) {\n      winston.debug('An error convert object message to string', err)\n    }\n\n    ws.send(message)\n  }\n\n}\n\n// var pubSub = new PubSub();\nmodule.exports = PubSub;\n"
  },
  {
    "path": "websocket/subscription.js",
    "content": "const  Map  = require('immutable').Map;\n// import uuid from 'uuid/v1'\nconst uuidv4 = require('uuid/v4');\nvar winston = require('../config/winston');\n\nclass Subscription {\n\n  constructor () {\n\n    this.subscriptions = new Map()\n  }\n\n  /**\n   * Return subsciption\n   * @param id\n   */\n  get (id) {\n    return this.subscriptions.get(id)\n  }\n\n  /**\n   * Add new subscription\n   * @param topic\n   * @param clientId\n   * @param type\n   * @returns {*}\n   */\n  add (topic, clientId, type = 'ws') {\n\n\n    // need to find subscription with same type = 'ws'\n\n    const findSubscriptionWithClientId = this.subscriptions.find(\n      (sub) => sub.clientId === clientId && sub.type === type && sub.topic === topic)\n\n    if (findSubscriptionWithClientId) {\n      // exist and no need add more subscription\n      return findSubscriptionWithClientId.id\n    }\n    const id = this.autoId()\n    const subscription = {\n      id: id,\n      topic: topic,\n      clientId: clientId,\n      type: type, // email, phone\n    }\n\n    // winston.debug('New subscriber via add method:', subscription)\n    this.subscriptions = this.subscriptions.set(id, subscription)\n    // winston.debug('New subscribers',this.subscriptions);\n    return id\n  }\n\n  /**\n   * Remove a subsciption\n   * @param id\n   */\n  remove (id) {\n\n    this.subscriptions = this.subscriptions.remove(id)\n  }\n\n  /**\n   * Clear all subscription\n   */\n  clear () {\n\n    this.subscriptions = this.subscriptions.clear()\n  }\n\n  /**\n   * Get Subscriptions\n   * @param predicate\n   * @returns {any}\n   */\n  getSubscriptions (predicate = null) {\n    return predicate\n    //   ? this.subscriptions\n      ? this.subscriptions.filter(predicate)\n      : this.subscriptions\n  }\n\n  /**\n   * Generate new ID\n   * @returns {*}\n   */\n  autoId () {\n    return uuidv4()\n  }\n}\n\n// var subscription = new Subscription();\nmodule.exports = Subscription;"
  },
  {
    "path": "websocket/webSocketServer.js",
    "content": "var Message = require(\"../models/message\");\nvar User = require(\"../models/user\");\nvar Project_user = require(\"../models/project_user\");\nvar Project = require(\"../models/project\");\nvar EventModel = require(\"../pubmodules/events/event\");\nvar Request = require(\"../models/request\");\nvar Message = require(\"../models/message\");\nvar Faq_kb = require(\"../models/faq_kb\");\nconst WebSocket = require('ws');\nvar url = require('url');\nvar validtoken = require('../middleware/valid-token');\nvar messageEvent = require(\"../event/messageEvent\");\nvar eventEvent = require(\"../pubmodules/events/eventEvent\");\nvar requestEvent = require(\"../event/requestEvent\");\nvar botEvent = require(\"../event/botEvent\");\nvar jwt = require('jsonwebtoken');\nvar config = require('../config/database'); // get db config file\nvar winston = require('../config/winston');\nvar roleChecker = require('../middleware/has-role');\nconst PubSub = require('./pubsub');\nconst authEvent = require('../event/authEvent');\nvar ProjectUserUtil = require(\"../utils/project_userUtil\");\nvar cacheUtil = require('../utils/cacheUtil');\nvar mongoose = require('mongoose');\nconst requestConstants = require(\"../models/requestConstants\");\nvar RoleConstants = require('../models/roleConstants');\nvar projectUserService = require(\"../services/projectUserService\");\n\nlet configSecretOrPubicKay = process.env.GLOBAL_SECRET || config.secret;\n\nvar pKey = process.env.GLOBAL_SECRET_OR_PUB_KEY;\n// console.log(\"pKey\",pKey);\n\nif (pKey) {\n  configSecretOrPubicKay = pKey.replace(/\\\\n/g, '\\n');\n}\n\n// Cache for tracking invalid token logs to prevent spam\nconst invalidTokenCache = new Map();\n\nfunction logInvalidToken(req, err) {\n  const ip = req.socket.remoteAddress;\n  const ua = req.headers['user-agent'] || 'unknown';\n  const now = Date.now();\n\n  const cacheKey = `${ip}_${req.url}`;\n  const lastLog = invalidTokenCache.get(cacheKey);\n\n  if (!lastLog || now - lastLog > 3600000) {\n    invalidTokenCache.set(cacheKey, now);\n    console.warn('[⚠️ INVALID WS TOKEN]', {\n      ip,\n      ua,\n      url: req.url,\n      time: new Date().toISOString(),\n      message: err.message,\n    });\n  }\n}\n\nvar cacheEnabler = require(\"../services/cacheEnabler\");\n\n\n\nvar lastRequestsLimit = process.env.WS_HISTORY_REQUESTS_LIMIT || 100;\nwinston.debug('lastRequestsLimit:' + lastRequestsLimit);\n\nlastRequestsLimit = Number(lastRequestsLimit);\n\nvar messagesLimit = process.env.WS_HISTORY_MESSAGES_LIMIT || 300;\nwinston.debug('messagesLimit:' + messagesLimit);\n\nvar lastEventsLimit = process.env.WS_HISTORY_EVENTS_LIMIT || 20;\nwinston.debug('lastEventsLimit:' + lastEventsLimit);\n\nvar websocketServerPath = process.env.WS_SERVER_PATH || '/';\nwinston.debug('websocketServerPath:' + websocketServerPath);\n\n\n\nclass WebSocketServer {\n\n  constructor() {\n    this.clientsSubscriptions = {};\n  }\n\n\n  // https://hackernoon.com/nodejs-web-socket-example-tutorial-send-message-connect-express-set-up-easy-step-30347a2c5535\n  // https://medium.com/@martin.sikora/node-js-websocket-simple-chat-tutorial-2def3a841b61\n  init(server) {\n\n    winston.info('Starting websocket on path: ' + websocketServerPath);\n\n    //var wss = new WebSocket.Server({ port: 40510 });\n    //var wss = new WebSocket.Server({ port: 40510 , path: \"/messages\" });\n    //var wss = new WebSocket.Server({  port: 80 ,path: \"/messages\" });\n    //  var wss = new WebSocket.Server({  server: server,path: \"/messages\" });\n\n    var wss = new WebSocket.Server({\n      server: server,\n      path: websocketServerPath,\n      verifyClient: function (info, cb) {\n        //console.log('info.req', info.req);\n        // var token = info.req.headers.Authorization\n        let urlParsed = url.parse(info.req.url, true);\n        // console.log('urlParsed', urlParsed);\n        var queryParameter = urlParsed.query;\n        winston.debug('queryParameter', queryParameter);\n\n        var token = queryParameter.token;\n        winston.debug('token:' + token);\n        winston.debug('configSecretOrPubicKay:' + configSecretOrPubicKay);\n\n\n        if (!token)\n          cb(false, 401, 'Unauthorized');\n        else {\n          token = token.replace('JWT ', '');\n          jwt.verify(token, configSecretOrPubicKay, function (err, decoded) {  //pub_jwt pp_jwt\n            if (err) {\n              // Log invalid token with rate limiting\n              logInvalidToken(info.req, err);\n              return cb(false, 401, 'Unauthorized');\n            } else {\n              // uncomment it\n              const identifier = decoded._id || decoded._doc._id;\n\n\n              winston.debug('valid token:' + identifier);\n              // roleChecker.hasRoleAsPromise().then(function(project_user) {\n              //   winston.debug('hasRoleAsPromise project_user',project_user);\n              // winston.debug('ok websocket');\n\n             User.findOne({ _id: identifier, status: 100 }, 'email firstname lastname emailverified id')     //TODO user_cache_here ma attento select.. ATTENTO SERVER SELECT??\n                //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, \"users:id:\"+identifier)    //user_cache\n                .exec(function (err, user) {\n\n\n                  if (err) {\n                    // console.log(\"BasicStrategy err.stop\");\n                    return winston.error('error verifing websocket jwt token. User find error ', err);\n                  }\n                  if (!user) {\n                    winston.verbose('websocket user not found with id : ' + identifier);\n                    return cb(false, 401, 'Unauthorized');\n                  }\n\n                  // info.req.user = decoded;\n                  info.req.user = user;\n                  winston.debug('info.req.user', info.req.user.toObject());\n                  return cb(true);\n\n                });\n\n\n              // }).catch(function(err){\n              //   winston.error('hasRoleAsPromise err',err);\n              //   cb(false, 401, err.msg);\n              // });                     \n\n            }\n          })\n\n        }\n      }\n    });\n\n\n    var onConnectCallback = async function (client, req) {\n      winston.debug('onConnectCallback ');\n      return new Promise(function (resolve, reject) {\n        return resolve(\"ok\");\n      });\n      // check here if you can subscript o publish message\n    }\n\n    var onDisconnectCallback = async function (subscript, id) {\n      winston.debug('onDisconnectCallback: ' + subscript + \":\" + id);\n      return new Promise(function (resolve, reject) {\n        return resolve(\"ok\");\n      });\n      // check here if you can subscript o publish message\n    }\n\n\n    //tilebaseMess.send('{ \"action\": \"publish\", \"payload\": { \"topic\": \"/apps/123/requests/sendid/conversations/RFN\", \"message\":{\"sender_id\":\"sendid\",\"sender_fullname\":\"SFN\", \"recipient_id\":\"RFN\", \"recipient_fullname\":\"RFN\",\"text\":\"hi\",\"app_id\":\"123\"}}}');\n    var onPublishCallback = async function (publishTopic, publishMessage, from) {\n      winston.debug(\"onPublish topic: \" + publishTopic + \" from: \" + from, publishMessage);\n      return new Promise(function (resolve, reject) {\n        return resolve(\"ok\");\n      });\n\n    }\n\n    var onMessageCallback = async function (id, message) {\n      winston.debug('onMessageCallback ', id, message);\n      return new Promise(function (resolve, reject) {\n        return resolve(\"ok\");\n      });\n      // check here if you can subscript o publish message\n    }\n\n    // tilebase.send('{ \"action\": \"subscribe\", \"payload\": { \"topic\": \"/app1/requests\"}}');\n\n    var onSubscribeCallback = async function (topic, clientId, req) {\n      return new Promise(function (resolve, reject) {\n        winston.debug('onSubscribeCallback :' + topic + \" \" + clientId);\n\n        winston.debug(' req.user._id: ' + req.user);\n\n        if (!topic) {\n          winston.error('WebSocket - Error getting  topic. Topic can t be null');\n          return reject('WebSocket - Error getting  topic. Topic can t be null');\n        }\n        var urlSub = topic.split('/');\n        winston.debug('urlSub: ' + urlSub);\n\n        if (!urlSub || (urlSub && urlSub.length == 0)) {\n          winston.error('WebSocket - Error getting  topic. Topic is not properly configured');\n          return reject('WebSocket - Error getting  topic. Topic is not properly configured');\n        }\n        // Error getting Project Cast to ObjectId failed for value \"N7VJlLZ1\" (type string) at path \"_id\" for model \"project\" {\"kind\":\"ObjectId\",\"path\":\"_id\",\"reason\":{},\"stack\":\"CastError: Cast to ObjectId failed for value \\\"N7VJlLZ1\\\" (type string) at path \\\"_id\\\" for model \\\"project\\\"\\n at model.Query.exec (/usr/src/app/node_modules/mongoose/lib/query.js:4498:21)\\n at /usr/src/app/websocket/webSocketServer.js:180:14\\n at new Promise (<anonymous>)\\n at Object.onSubscribeCallback [as onSubscribe] (/usr/src/app/websocket/webSocketServer.js:167:14)\\n at PubSub.handleReceivedClientMessage (/usr/src/app/websocket/pubsub.js:358:57)\\n at runMicrotasks (<anonymous>)\\n at processTicksAndRejections (internal/process/task_queues.js:97:5)\",\"stringValue\":\"\\\"N7VJlLZ1\\\"\",\"value\":\"N7VJlLZ1\",\"valueType\":\"string\"}\n\n        var projectId = urlSub[1];\n        winston.debug('projectId: ' + projectId);\n        \n        let q = Project.findOne({ _id: projectId, status: 100 })\n\n        if (cacheEnabler.project) {\n          q.cache(cacheUtil.defaultTTL, \"projects:id:\" + projectId) //project_cache\n          winston.debug('project cache enabled for websocket');\n        }\n\n        return q.exec(async (err, project) => {\n          if (err) {\n            winston.error('WebSocket - Error getting  Project', err);\n            return reject(err);\n          }\n\n          if (!project) {\n            winston.warn('WebSocket project not found for projectid ' + projectId);\n            return reject({ err: 'project not found for projectid ' + projectId });\n          }\n\n          if (topic.endsWith('/messages')) {\n            winston.debug(' messages: ');\n\n            var recipientId = urlSub[3];\n            winston.debug('recipientId: ' + recipientId);\n            // winston.debug(' req.: ',req);\n\n\n            try {\n              var projectuser = await projectUserService.getWithPermissions(req.user._id, projectId, req.user.sub);            \n            } catch(err) {\n              winston.error('WebSocket error getting Project_user', err);\n              return reject(err);\n            }\n\n            // Project_user.findOne({ id_project: projectId, id_user: req.user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" })\n            //   //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":project_users:role:teammate:\"+req.user._id)\n            //   .exec(function (err, projectuser) {\n            //     if (err) {\n            //       winston.error('WebSocket error getting Project_user', err);\n            //       return reject(err);\n            //     }\n              if (!projectuser) {\n                winston.verbose('WebSocket project_user not found for user id ' + req.user._id + ' and projectid ' + projectId);\n                return reject({ err: 'Project_user not found for user id ' + req.user._id + ' and projectid ' + projectId });\n              }\n\n              var queryRequest = { id_project: projectId, request_id: recipientId };\n\n              // request_role_check_imp              \n              if (projectuser.hasPermissionOrRole('request_read_all', [\"owner\", \"admin\"])) {\n                winston.debug('queryRequest admin: ' + JSON.stringify(queryRequest));\n              } \n              else if (projectuser.hasPermissionOrRole('request_read_group', [\"agent\"])) {\n                queryRequest[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }]\n              } else {\n                winston.debug('queryRequest agent: ' + JSON.stringify(queryRequest));\n                queryRequest[\"participants\"] = req.user.id;\n              }\n\n              // requestcachefarequi nocachepopulatereqired\n              winston.debug(\"main_flow_cache_3 websocket1\");\n              \n              Request.findOne(queryRequest)\n                .exec(function (err, request) {\n\n                  if (err) {\n                    winston.error('WebSocket Error finding request for onSubscribeCallback', err);\n                    return reject(err);\n                  }\n                  if (!request) {\n                    winston.verbose('WebSocket Request query not found for user id ' + req.user._id + ' and projectid ' + projectId);\n                    return reject({ err: 'Request query not found for user id ' + req.user._id + ' and projectid ' + projectId });\n                  }\n\n                  winston.debug('found request for onSubscribeCallback', request);\n\n\n\n                  var query = { id_project: projectId, recipient: recipientId };\n                  winston.debug('query : ' + JSON.stringify(query));\n\n                  Message.find(query).sort({ createdAt: 'asc' })\n                    .limit(messagesLimit).exec(function (err, messages) {\n\n                      if (err) {\n                        winston.error('WebSocket Error finding message for onSubscribeCallback', err);\n                        return reject(err);\n                      }\n                      winston.debug('onSubscribeCallback find', messages);\n\n\n                      return resolve({\n                        publishFunction: function () {\n                          // handlePublishMessageToClientId (topic, message, clientId, method) {\n                          pubSubServer.handlePublishMessageToClientId(topic, messages, clientId, \"CREATE\");\n                        }\n                      });\n\n                    });\n                });\n\n              // });\n\n          } else if (topic.endsWith('/requests')) {\n\n            winston.debug('req.user._id: ' + req.user._id);\n            // winston.debug(' req.: ',req);\n\n            winston.debug('find project_user');\n\n\n            try {\n              var projectuser = await projectUserService.getWithPermissions(req.user._id, projectId, req.user.sub);            \n            } catch(err) {\n              winston.error('WebSocket error getting Project_user', err);\n              return reject(err);\n            }\n            // Project_user.findOne({ id_project: projectId, id_user: req.user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" })\n            //   //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":project_users:role:teammate:\"+req.user._id)\n            //   .exec(function (err, projectuser) {\n            //     if (err) {\n            //       winston.error('WebSocket error getting Project_user', err);\n            //       return reject(err);\n            //     }\n            if (!projectuser) {\n              winston.verbose('WebSocket Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId);\n              return reject({ err: 'Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId });\n            }\n            winston.debug('projectuser', projectuser.toObject());\n\n            var query = { \"id_project\": projectId, \"status\": { $lt: 1000, $gt: 50, $ne: 150 }, preflight: false, \"draft\": { $in: [false, null] } };\n            \n            if (projectuser.hasPermissionOrRole('request_read_all', [\"owner\", \"admin\"])) {\n              winston.debug('ws requests query admin: ' + JSON.stringify(query));\n            } else if (projectuser.hasPermissionOrRole('request_read_group', [\"agent\"])) {\n              query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }];\n              winston.debug('ws requests query agent: ' + JSON.stringify(query));\n            } else {\n              query[\"participants\"] = req.user.id;\n              winston.debug('ws requests query agent limited: ' + JSON.stringify(query));                \n            }\n\n\n            //cacheimportantehere\n            // requestcachefarequi populaterequired\n            winston.debug('found Request.find(query)');\n\n            //  TODO  proviamo a fare esempio con 100 agenti tutti\n            // elimina capo availableAgents (chiedi a Nico se gli usa altrimenti metti a select false)\n            var startDate = new Date();\n            Request.find(query)\n              .select(\"+snapshot.agents\")\n              // .populate('lead') //??\n              // .populate('department')\n              // .populate('participatingBots')\n              // .populate('participatingAgents')  \n              // .populate({path:'requester',populate:{path:'id_user'}})\n              .sort({ updatedAt: 'desc' })\n              .limit(lastRequestsLimit)\n              // DISABLED 23Marzo2021 per problema request.snapshot.requester.isAuthenticated = undefined \n              .lean() //https://www.tothenew.com/blog/high-performance-find-query-using-lean-in-mongoose-2/ https://stackoverflow.com/questions/33104136/mongodb-mongoose-slow-query-when-fetching-10k-documents\n              //@DISABLED_CACHE .cache(cacheUtil.queryTTL, projectId+\":requests:query:status-50-1000:preflight-false:select_snapshot_agents:\"+cacheUserId) \n              .exec(function (err, requests) {\n\n                if (err) {\n                  winston.error('WebSocket Error finding request for onSubscribeCallback', err);\n                  return reject(err);\n                }\n                winston.debug('found requests for onSubscribeCallback', requests);\n\n                if (requests && requests.length > 0) {\n                  requests.forEach(request => {\n\n                    request.id = request._id; //importante\n\n\n                    if (request.lead) {\n                      //    request.requester_id =    request.lead._id; //parla con NICO di questo\n                      request.requester_id = request.lead;\n                    } else {\n                      request.requester_id = null;\n                    }\n\n                    if (request.snapshot.requester) {\n                      if (request.snapshot.requester.role === RoleConstants.GUEST) {\n                        request.snapshot.requester.isAuthenticated = false;\n                      } else {\n                        request.snapshot.requester.isAuthenticated = true;\n                      }\n\n                    }\n\n                    // attento qui\n                    if (request.snapshot.agents && request.snapshot.agents.length > 0) {\n                      var agentsnew = [];\n                      request.snapshot.agents.forEach(a => {\n                        agentsnew.push({ id_user: a.id_user })  //remove unnecessary request.agents[].project_user fields. keep only id_user\n                      });\n                      request.snapshot.agents = agentsnew;\n                    }\n\n\n\n\n                  });\n                }\n\n                var endDate = new Date();\n                winston.debug('ws count: ' + query + ' ' + requests.length + ' ' + startDate + ' ' + endDate + ' ' + endDate - startDate)\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, requests, clientId, \"CREATE\");\n                  }\n                });\n\n\n              });\n\n              // });\n\n\n          } else if (topic.indexOf('/project_users/users/') > -1) {\n\n            var userId = urlSub[4];\n            winston.debug('userId: ' + userId);\n\n            try {\n              var currentProjectuser = await projectUserService.getWithPermissions(req.user._id, projectId, req.user.sub);            \n            } catch(err) {\n              winston.error('WebSocket error getting Project_user', err);\n              return reject(err);\n            }\n\n            // //check if current user can see the data\n            // Project_user.findOne({ id_project: projectId, id_user: req.user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" })\n            //   //@DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":project_users:role:teammate:\"+req.user._id)\n            //   .exec(function (err, currentProjectuser) {\n            //     if (err) {\n            //       winston.error('WebSocket error getting  Project_user', err);\n            //       return reject(err);\n            //     }\n            if (!currentProjectuser) {\n              winston.verbose('WebSocket Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId);\n              return reject({ err: 'Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId });\n            }\n            winston.debug('currentProjectuser', currentProjectuser.toObject());\n\n\n            var isObjectId = mongoose.Types.ObjectId.isValid(userId);\n            winston.debug(\"isObjectId:\" + isObjectId);\n\n            var query = { id_project: projectId, status: \"active\" };\n            winston.debug(' query: ', query);\n\n            if (isObjectId) {\n              query.id_user = userId;\n            } else {\n              query.uuid_user = userId;\n            }\n\n            Project_user.findOne(query)\n              // @DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":project_users:users:\"+userId)\n              .exec(function (err, projectuser) {\n                if (err) {\n                  winston.error('WebSocket error getting  Project_user', err);\n                  return reject(err);\n                }\n                if (!projectuser) {\n                  winston.verbose('WebSocket Project_user not found with user id ' + userId + ' and projectid ' + projectId);\n                  return reject({ err: 'Project_user not found with user id ' + userId + ' and projectid ' + projectId });\n                }\n\n\n                var pu = projectuser.toJSON();\n                pu.isBusy = ProjectUserUtil.isBusy(projectuser, project.settings && project.settings.max_agent_assigned_chat);\n\n\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, pu, clientId, \"CREATE\");\n                  }\n                });\n\n              });\n\n\n\n\n              // });\n\n\n            // tilebase.send('{ \"action\": \"subscribe\", \"payload\": { \"topic\": \"/5e71139f61dd040bc9594cee/project_users/5e71139f61dd040bc9594cef\"}}')\n            //curl -v -X PUT -H 'Content-Type:application/json' -u andrea.leo@f21.it:123456 -d '{\"user_available\":false}' http://localhost:3000/5e71139f61dd040bc9594cee/project_users/\n          } else if (topic.indexOf('/project_users/') > -1) {\n\n\n            // TODO aggiungere tutti i prject users\n\n            var puId = urlSub[3];\n            winston.debug('puId: ' + puId);\n\n            //var query = { _id: puId, id_project: projectId};\n            var query = { _id: puId, id_project: projectId, id_user: req.user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" };\n            winston.debug(' query: ', query);\n\n            Project_user.findOne(query)\n              // @DISABLED_CACHE  .cache(cacheUtil.defaultTTL, projectId+\":project_users:\"+puId)\n              .exec(function (err, projectuser) {\n                if (err) {\n                  winston.error('WebSocket error getting  Project_user', err);\n                  return reject(err);\n                }\n                if (!projectuser) {\n                  winston.verbose('WebSocket Project_user not found with project_user id ' + puId + ' and projectid ' + projectId);\n                  return reject({ err: 'Project_user not found with project_user id ' + puId + ' and projectid ' + projectId });\n                }\n\n\n                var pu = projectuser.toJSON();\n                pu.isBusy = ProjectUserUtil.isBusy(projectuser, project.settings && project.settings.max_agent_assigned_chat);\n\n\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, pu, clientId, \"CREATE\");\n                  }\n                });\n\n              });\n          }\n\n          else if (topic.indexOf('/events/') > -1) {\n\n            var puId = urlSub[3];\n            winston.debug('puId: ' + puId);\n\n            var eventName = urlSub[4];\n            winston.debug('eventName: ' + eventName);\n\n            var query = { project_user: puId, id_project: projectId };\n            if (eventName) {\n              query.name = eventName;\n            }\n            //TODO esclude status volatile events\n            winston.debug(' query: ', query);\n\n            EventModel.find(query)\n              // @DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":events:\"+puId)\n              .sort({ createdAt: 'desc' })\n              .limit(lastEventsLimit)\n              .exec(function (err, events) {\n                if (err) {\n                  winston.error('WebSocket error getting  events', err);\n                  return reject(err);\n                }\n\n\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, events, clientId, \"CREATE\");\n                  }\n                });\n\n              })\n\n          } else if (topic.indexOf('/bots/') > -1) {\n\n            // var puId = urlSub[3];\n            // winston.info('puId: '+puId);\n\n            winston.debug('urlSub: ' + urlSub);\n\n            var botId = urlSub[3];\n            winston.debug('botId: ' + botId);\n\n            var query = { _id: botId, id_project: projectId };\n\n            winston.debug(' query: ', query);\n\n            Faq_kb.findOne(query)\n              .exec(function (err, bot) {\n                if (err) {\n                  winston.error('WebSocket error getting  bots', err);\n                  return reject(err);\n                }\n\n\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, bot, clientId, \"CREATE\");\n                  }\n                });\n\n              });\n\n          } else {\n\n            //request/id\n\n            // winston.debug(' req.: ',req);\n\n            var recipientId = urlSub[3];\n            winston.debug('recipientId: ' + recipientId);\n\n            try {\n              var projectuser = await projectUserService.getWithPermissions(req.user._id, projectId, req.user.sub);            \n            } catch(err) {\n              winston.error('WebSocket error getting Project_user', err);\n              return reject(err);\n            }\n\n            // Project_user.findOne({ id_project: projectId, id_user: req.user._id, roleType: RoleConstants.TYPE_AGENTS, status: \"active\" })\n            //   // @DISABLED_CACHE .cache(cacheUtil.defaultTTL, projectId+\":project_users:role:teammate:\"+req.user._id)\n            //   .exec(function (err, projectuser) {\n            //     if (err) {\n            //       winston.error('WebSocket error getting Project_user', err);\n            //       return reject(err);\n            //     }\n            if (!projectuser) {\n              winston.verbose('WebSocket Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId);\n              return reject({ err: 'Project_user not found with user id ' + req.user._id + ' and projectid ' + projectId });\n            }\n\n            var query = { id_project: projectId, request_id: recipientId };\n            winston.debug('query: ' + JSON.stringify(query));\n\n            // request_role_check\n\n            if (projectuser.hasPermissionOrRole('request_read_all', [\"owner\", \"admin\"])) {\n              winston.debug('query admin: ' + JSON.stringify(query));\n            } else if (projectuser.hasPermissionOrRole('request_read_group', [\"agent\"])) {\n\n              query[\"$or\"] = [{ \"snapshot.agents.id_user\": req.user.id }, { \"participants\": req.user.id }]\n\n            } \n            // else if (projectuser.hasPermissionOrRole('request_read_mine', [\"????\"])) {\n            //   query[\"participants\"] = req.user.id;\n            // }\n            else {\n              query[\"participants\"] = req.user.id;\n              // generate empty requests response\n            }\n\n\n            // requestcachefarequi populaterequired\n            winston.debug('info: main_flow_cache_2.2');\n\n            Request.findOne(query)\n              .populate('lead')\n              .populate('department')\n              .populate('participatingBots')\n              .populate('participatingAgents')\n              .populate({ path: 'requester', populate: { path: 'id_user' } })\n              .sort({ updatedAt: 'asc' }).exec(function (err, request) {\n\n                if (err) {\n                  winston.error('WebSocket Error finding request for onSubscribeCallback', err);\n                  return reject(err);\n                }\n                winston.debug('onSubscribeCallback find', request);\n\n                return resolve({\n                  publishFunction: function () {\n                    // handlePublishMessageToClientId (topic, message, clientId, method) {\n                    pubSubServer.handlePublishMessageToClientId(topic, request, clientId, \"CREATE\");\n                  }\n                });\n\n              });\n\n              // });\n\n          }\n\n\n\n\n        });\n\n      });\n    }\n\n\n\n\n    const pubSubServer = new PubSub(wss, {\n      onConnect: onConnectCallback, onDisconnect: onDisconnectCallback,\n      onMessage: onMessageCallback, onSubscribe: onSubscribeCallback, onPublish: onPublishCallback\n    });\n\n    var that = this;\n\n\n    var messageCreateKey = 'message.create';\n    if (messageEvent.queueEnabled) {\n      messageCreateKey = 'message.create.queue.pubsub';\n    }\n    winston.debug('WS messageCreateKey: ' + messageCreateKey);\n\n    messageEvent.on(messageCreateKey, function (message) {\n      setImmediate(async () => {\n        winston.debug('messageEvent websocket server: ' + messageCreateKey, message);\n        if (message.request) {\n          pubSubServer.handlePublishMessage('/' + message.id_project + '/requests/' + message.request.request_id + '/messages', message, undefined, true, \"CREATE\");\n        }\n      });\n    });\n\n    // var reconnect = require('./reconnect');\n    var requestCreateKey = 'request.create';\n    if (requestEvent.queueEnabled) {\n      requestCreateKey = 'request.create.queue.pubsub';\n    }\n    winston.debug('requestCreateKey: ' + requestCreateKey);\n    requestEvent.on(requestCreateKey, async function (request) {\n      setImmediate(async () => {\n\n        winston.debug('requestEvent websocket server: ' + requestCreateKey, request);\n        // TODO scarta riquesta se agente (req.user._id) non sta ne in participants ne in agents\n\n        if (request.preflight === false) {\n\n          //TODO ATTENTION change value by reference requestJSON.snapshot\n          let requestJSON = Object.assign({}, request);\n          // var requestJSON = request;\n\n          if (request.toObject) {\n            requestJSON = request.toObject();\n          }\n\n          //deleted snapshot department lead, etc..\n          delete requestJSON.snapshot;\n          requestJSON.snapshot = {};\n\n\n\n\n          // ATTENTO  https://stackoverflow.com/questions/64059795/mongodb-get-error-message-mongoerror-path-collision-at-activity\n          try {\n            winston.debug(\"main_flow_cache_ws request find\");\n            \n            var snapshotAgents = await Request.findById(request.id).select({ \"snapshot\": 1 }).exec(); //SEMBRA CHE RITORNI TUTTO LO SNAPSHOT INVECE CHE SOLO AGENTS\n            winston.debug('snapshotAgents', snapshotAgents);\n            // requestJSON.snapshot.agents = snapshotAgents;\n\n            if (snapshotAgents.snapshot.agents && snapshotAgents.snapshot.agents.length > 0) {\n              var agentsnew = [];\n              snapshotAgents.snapshot.agents.forEach(a => {\n                agentsnew.push({ id_user: a.id_user })\n              });\n              //TODO ATTENTION change value by reference  requestJSON.snapshot.agents. but it's fixed with line 640\n              requestJSON.snapshot.agents = agentsnew;\n            }\n            winston.debug('requestJSON', requestJSON);\n          } catch (e) {\n            winston.error('Error getting snapshotAgents in ws. This is a mongo issue', e);\n          }\n\n          // aggiungere controllo sui permessi qui\n          pubSubServer.handlePublishMessage('/' + request.id_project + '/requests', request, undefined, true, \"CREATE\");\n          pubSubServer.handlePublishMessage('/' + request.id_project + '/requests/' + request.request_id, request, undefined, true, \"CREATE\");\n        }\n      });\n    });\n\n    var requestUpdateKey = 'request.update';\n    if (requestEvent.queueEnabled) {\n      requestUpdateKey = 'request.update.queue.pubsub';\n    }\n\n    winston.debug('requestUpdateKey: ' + requestUpdateKey);\n    requestEvent.on(requestUpdateKey, async function (request) {\n      setImmediate(async () => {\n\n        // TODO setImmediate(() => {        \n        winston.debug('requestEvent websocket server: ' + requestUpdateKey, request);\n        if (request.preflight === false && request.status > requestConstants.TEMP) {\n\n          //TODO ATTENTION change value by reference requestJSON.snapshot\n          let requestJSON = Object.assign({}, request);\n          // var requestJSON = request;\n\n          if (request.toObject) {\n            requestJSON = request.toObject();\n          }\n\n          //deleted snapshot department lead, etc..\n          delete requestJSON.snapshot;\n          requestJSON.snapshot = {};\n\n          // ATTENTO  https://stackoverflow.com/questions/64059795/mongodb-get-error-message-mongoerror-path-collision-at-activity\n\n          try {\n            winston.debug(\"main_flow_cache_ws request find 2\");\n            // cache_next\n            var snapshotAgents = await Request.findById(request.id).select({ \"snapshot\": 1 }).exec(); //SEMBRA CHE RITORNI TUTTO LO SNAPSHOT INVECE CHE SOLO AGENTS\n            winston.debug('snapshotAgents', snapshotAgents);\n            // requestJSON.snapshot.agents = snapshotAgents;\n\n            if (snapshotAgents.snapshot.agents && snapshotAgents.snapshot.agents.length > 0) {\n              var agentsnew = [];\n              snapshotAgents.snapshot.agents.forEach(a => {\n                agentsnew.push({ id_user: a.id_user })\n              });\n              //TODO ATTENTION change value by reference requestJSON.snapshot.agents . fixed with line 693\n              requestJSON.snapshot.agents = agentsnew;\n            }\n            winston.debug('requestJSON', requestJSON);\n\n          } catch (e) {\n            winston.error('Error getting snapshotAgents in ws. This is a mongo issue', e);\n          }\n\n          // aggiungere controllo sui permessi qui\n\n          if (requestJSON.draft !== true) {\n            pubSubServer.handlePublishMessage('/' + request.id_project + '/requests', requestJSON, undefined, true, \"UPDATE\");\n          }\n          pubSubServer.handlePublishMessage('/' + request.id_project + '/requests/' + request.request_id, requestJSON, undefined, true, \"UPDATE\");\n\n        }\n      });\n    });\n\n\n\n\n    // TODO request.close is missing?\n\n\n    var projectuserUpdateKey = 'project_user.update';\n    if (authEvent.queueEnabled) {\n      projectuserUpdateKey = 'project_user.update.queue.pubsub';\n    }\n    winston.debug('projectuserUpdateKey: ' + projectuserUpdateKey);\n    authEvent.on(projectuserUpdateKey, function (data) {\n      setImmediate(async () => {\n\n        var pu = data.updatedProject_userPopulated;\n        winston.debug('ws pu', pu);\n\n        //TODO pubSubServer.handlePublishMessage ('/'+pu.id_project+'/project_users/', pu, undefined, true, \"UPDATE\");\n\n        pubSubServer.handlePublishMessage('/' + pu.id_project + '/project_users/' + pu.id, pu, undefined, true, \"UPDATE\");\n\n        var userId;\n        if (pu.uuid_user) {\n          userId = pu.uuid_user;\n        } else {\n          userId = pu.id_user._id;\n        }\n        winston.debug('userId:' + userId);\n        pubSubServer.handlePublishMessage('/' + pu.id_project + '/project_users/users/' + userId, pu, undefined, true, \"UPDATE\");\n      });\n    });\n\n\n\n    var eventEmitKey = 'event.emit';\n    if (eventEvent.queueEnabled) {\n      eventEmitKey = 'event.emit.queue.pubsub';\n    }\n    winston.debug('eventEmitKey: ' + eventEmitKey);\n    eventEvent.on(eventEmitKey, function (event) {\n      setImmediate(async () => {\n        winston.debug('event', event);\n        if (event.project_user === undefined) {\n          //with \"faqbot.answer_not_found\" project_user is undefined but it's ok \n          winston.debug('not sending with ws event with project_user undefined', event);\n          return;\n        }\n        pubSubServer.handlePublishMessage('/' + event.id_project + '/events/' + event.project_user._id, event, undefined, true, \"CREATE\");\n      });\n    });\n\n\n\n\n\n\n\n\n    var botUpdateKey = 'faqbot.update';\n    if (botEvent.queueEnabled) {\n      botUpdateKey = 'faqbot.update.queue.pubsub';\n    }\n\n    winston.debug('botUpdateKey: ' + botUpdateKey);\n    botEvent.on(botUpdateKey, async function (bot) {\n      setImmediate(async () => {\n\n        // TODO setImmediate(() => {        \n\n        let botJSON = Object.assign({}, bot);\n\n        if (bot.toObject) {\n          botJSON = bot.toObject();\n        }\n\n        let topic = '/' + bot.id_project + '/bots/' + bot._id;\n        winston.debug('botEvent websocket server: ' + botUpdateKey + \" on topic \" + topic, botJSON);\n\n\n        pubSubServer.handlePublishMessage(topic, botJSON, undefined, true, \"UPDATE\");\n      });\n    });\n\n\n\n\n\n    // https://github.com/websockets/ws/blob/master/examples/express-session-parse/index.js\n\n\n    // IMPORTANTE https://blog.usejournal.com/using-mongodb-as-realtime-db-with-nodejs-c6f52c266750\n\n    // start mongo with: mongod --port 27017 --replSet rs0\n    //     wss.on('connection', function connection(ws, req) {\n\n    //       winston.debug('ws connection. req.url)', req.url);\n    //        //console.log('ws connection. req', req);\n\n    //        let urlParsed = url.parse(req.url, true);\n    //        winston.debug('urlParsed', urlParsed);\n\n    //        var queryParameter = urlParsed.query;\n    //        winston.debug('queryParameter', queryParameter);\n\n    //        var id_project = queryParameter.id_project;    \n    //        winston.debug('id_project=', id_project);   \n\n    //        if (!id_project) {\n    //           winston.error('id_project not specified');   \n    //           return 0;\n    //        }\n\n    //        winston.debug('queryParameter.events'+ queryParameter.events);\n    //        var events = JSON.parse(queryParameter.events);    \n    //        winston.debug('events', events);  \n\n    //        if (!events) {\n    //         winston.error('events not specified');   \n    //         return 0;\n    //        }\n\n    //        that.subscribeClient(id_project, events, ws);\n\n\n\n    //        winston.debug('queryParameter.q', queryParameter.q);\n\n    //        var query = JSON.parse(queryParameter.q);  \n    //        query['id_project'] = id_project;         \n    //        winston.debug('query=', query);\n\n    //        that.sendInitialData(events,query,ws);\n\n    //       // https://www.sitepoint.com/build-node-js-powered-chatroom-web-app-node-mongodb-socket/\n    //       // usa stream come qui \n\n\n\n\n\n\n\n\n    //       // message2Event.on(id_project+'.message.create', function (message) {\n    //       //   console.log('message2Event=', message);\n    //       //   ws.send(JSON.stringify(message));\n    //       // });\n\n\n\n\n    // // // with mongo stream start\n    // //       //const fullDocumentQuery = {fullDocument: query};\n    // //       const fullDocumentQuery = that.cloneAsDotted(\"fullDocument.\", query);\n    // //       console.log(\"fullDocumentQuery\", JSON.stringify(fullDocumentQuery));\n\n    // //       const pipeline = [{ $match: fullDocumentQuery }];\n    // //       console.log(\"pipeline\", JSON.stringify(pipeline));\n\n    // //       //const changeStream = Message.watch( pipeline, { fullDocument: 'updateLookup' });\n    // //       const changeStream = Message.watch( pipeline);\n    // //        //const changeStream = Message.watch();\n\n    // //       console.log(\"changeStream\"); \n    // //       changeStream.on('change', (change) => {\n    // //         console.log('change', change); // You could parse out the needed info and send only that data. \n    // //         ws.send(JSON.stringify(change.fullDocument));\n    // //         //io.emit('changeData', change);\n    // //        }); \n\n    // //       //  with mongo stream end\n\n\n\n\n\n\n    //       ws.on('message', function incoming(message) {\n    //         if (message.type === 'utf8') {\n    //           // process WebSocket message\n\n    //         }\n    //         console.log('received: %s', message);\n\n    //       });\n\n    //       // ws.on('open', function open() {\n    //       //   console.log('ws open');\n    //       // });\n\n    //     });\n\n\n\n  }\n\n  // sendInitialData(events,query,ws) {\n  //   if (events && events.length>0) {\n  //     events.forEach(function(event) {\n  //       if (event=='message') {          \n  //         Message.find(query).sort({updatedAt: 'asc'}).limit(20).exec(function(err, messages) {   \n  //           var dataToSend = {event:event, data:messages};        \n  //           ws.send(JSON.stringify(dataToSend));\n  //         });\n  //       }\n\n  //       if (event=='request') {\n  //         Request.find(query).sort({updatedAt: 'asc'}).limit(20).exec(function(err, requests) {   \n  //           var dataToSend = {event:event, data:requests};                \n  //           ws.send(JSON.stringify(dataToSend));\n  //         });\n  //       }\n\n  //     });\n  //   }\n  // }\n\n  // subscribeClient(id_project, events, ws) {\n  //   if (!this.clientsSubscriptions[id_project]) {\n  //     this.clientsSubscriptions[id_project]={};\n  //    }\n  //    var that=this;\n\n  //    if (events && events.length>0) {\n  //     events.forEach(function(event) {\n  //       if (!that.clientsSubscriptions[id_project][event]) {\n  //         that.clientsSubscriptions[id_project][event]=[];\n  //        }\n  //        that.clientsSubscriptions[id_project][event].push(ws);\n  //      });\n  //    }\n\n\n  //    winston.debug('clientsSubscriptions=', this.clientsSubscriptions);\n  // }\n\n  // sendAll(data, event){\n  //   var id_project = data.id_project;\n  //   winston.debug(\"id_project:\"+id_project);\n\n  //   winston.debug(\"event:\"+event);\n\n  //   var dataToSend = {event:event, data:data};\n\n  //   winston.debug(\"this.clientsSubscriptions:\",this.clientsSubscriptions);\n\n  //   if (this.clientsSubscriptions[id_project] && \n  //         this.clientsSubscriptions[id_project][event] && \n  //         this.clientsSubscriptions[id_project][event].length>0) {\n\n  //     this.clientsSubscriptions[id_project][event].forEach(function(client) {\n  //       winston.debug(\"send client:\", client);\n  //       client.send(JSON.stringify(dataToSend));\n  //     });\n\n  //   }\n\n  // }\n\n\n\n  // cloneAsDotted(prefix, origin) {\n  //   // // Don't do anything if add isn't an object\n  //   // if (!add || typeof add !== 'object') return origin;\n  //   var newObj = {};\n\n  //   var keys = Object.keys(origin);\n  //   var i = keys.length;\n  //   while (i--) {\n  //     newObj[prefix+keys[i]] = origin[keys[i]];\n  //   }\n  //   return newObj;\n  // }\n\n\n\n}\n\nvar webSocketServer = new WebSocketServer();\nmodule.exports = webSocketServer;\n"
  }
]