diff --git a/.circleci/config.yml b/.circleci/config.yml index 8d6ca5b206c7..bbad59281e8c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -44,7 +44,7 @@ test-save-npm-cache: &test-save-npm-cache - ./node_modules test-docker-image: &test-docker-image - circleci/node:8.15-stretch-browsers + circleci/node:8.16-stretch-browsers test-with-oplog: &test-with-oplog <<: *defaults @@ -69,7 +69,7 @@ jobs: build: &build <<: *defaults docker: - - image: circleci/node:8.15-stretch + - image: circleci/node:8.16-stretch - image: mongo:3.4 steps: @@ -147,7 +147,7 @@ jobs: - run: name: Build Rocket.Chat environment: - TOOL_NODE_FLAGS: --max_old_space_size=3072 + TOOL_NODE_FLAGS: --max_old_space_size=4096 command: | if [[ $CIRCLE_TAG ]] || [[ $CIRCLE_BRANCH == 'develop' ]]; then meteor reset; @@ -220,7 +220,7 @@ jobs: deploy: <<: *defaults docker: - - image: circleci/node:8.15-stretch + - image: circleci/node:8.16-stretch steps: - attach_workspace: @@ -268,7 +268,7 @@ jobs: export CIRCLE_TAG=${CIRCLE_TAG:=} - cp ~/repo/.docker/Dockerfile.local ./Dockerfile + cp ~/repo/.docker/Dockerfile . docker login -u $DOCKER_USER -p $DOCKER_PASS if [[ $CIRCLE_TAG ]]; then @@ -314,7 +314,16 @@ workflows: version: 2 build-and-test: jobs: + - hold-all: + type: approval + filters: + branches: + only: develop + tags: + only: /^[0-9]+\.[0-9]+\.[0-9]+(?:-(?:rc|beta)\.[0-9]+)?$/ - build: + requires: + - hold-all filters: tags: only: /^v[0-9]+\.[0-9]+\.[0-9]-[0-9]+\.[0-9]+\.[0-9]+$/ diff --git a/.circleci/docker.sh b/.circleci/docker.sh deleted file mode 100644 index 4edf27c21775..000000000000 --- a/.circleci/docker.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -CURL_URL="https://registry.hub.docker.com/u/rocketchat/rocket.chat/trigger/$DOCKER_TRIGGER_TOKEN/" - -if [[ $CIRCLE_TAG ]]; then - CURL_DATA='{"source_type":"Tag","source_name":"'"$CIRCLE_TAG"'"}'; -else - CURL_DATA='{"source_type":"Branch","source_name":"'"$CIRCLE_BRANCH"'"}'; -fi - -curl -H "Content-Type: application/json" --data "$CURL_DATA" -X POST "$CURL_URL" diff --git a/.circleci/snap.sh b/.circleci/snap.sh index d4d30c371248..afd047de0e15 100644 --- a/.circleci/snap.sh +++ b/.circleci/snap.sh @@ -3,7 +3,9 @@ set -euvo pipefail IFS=$'\n\t' # Add launchpad to known hosts -ssh-keyscan -t rsa -H git.launchpad.net > ~/.ssh/known_hosts + +mkdir -p $HOME/.ssh +ssh-keyscan -t rsa -H git.launchpad.net >> $HOME/.ssh/known_hosts echo "Preparing to trigger a snap release for $SNAP_CHANNEL channel" diff --git a/.docker/Dockerfile b/.docker/Dockerfile index 0f4d275a87d3..1c2a93fe8077 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -1,15 +1,10 @@ -FROM rocketchat/base:8 +FROM rocketchat/base:8.17.0 -ENV RC_VERSION 2.1.2 -MAINTAINER buildmaster@rocket.chat +ADD . /app + +LABEL maintainer="buildmaster@rocket.chat" RUN set -x \ - && curl -SLf "https://releases.rocket.chat/${RC_VERSION}/download/" -o rocket.chat.tgz \ - && curl -SLf "https://releases.rocket.chat/${RC_VERSION}/asc" -o rocket.chat.tgz.asc \ - && gpg --verify rocket.chat.tgz.asc \ - && mkdir -p /app \ - && tar -zxf rocket.chat.tgz -C /app \ - && rm rocket.chat.tgz rocket.chat.tgz.asc \ && cd /app/bundle/programs/server \ && npm install \ && npm cache clear --force \ diff --git a/.docker/Dockerfile.rhel b/.docker/Dockerfile.rhel index 7347f3f8be08..99f2b51c9770 100644 --- a/.docker/Dockerfile.rhel +++ b/.docker/Dockerfile.rhel @@ -1,6 +1,6 @@ FROM registry.access.redhat.com/rhscl/nodejs-8-rhel7 -ENV RC_VERSION 2.3.2 +ENV RC_VERSION 2.4.7 MAINTAINER buildmaster@rocket.chat diff --git a/.docker/startup.sh b/.docker/startup.sh deleted file mode 100644 index 1bba3b95a7f1..000000000000 --- a/.docker/startup.sh +++ /dev/null @@ -1,15 +0,0 @@ -# in a container environment, we may need to determine the INSTANCE_IP -# for each container so that uploads are working even if load-balanced in a group -if [ -z $INSTANCE_IP ] -then - export INSTANCE_IP=$(hostname -i) - echo "Got INSTANCE_IP via hostname: $INSTANCE_IP" -fi - -if [ -z $INSTANCE_IP ] -then - export INSTANCE_IP=$(curl -s http://169.254.169.254/latest/meta-data/local-ipv4 --connect-timeout 2) - echo "Got INSTANCE_IP via ECS metadata API: $INSTANCE_IP" -fi - -node main.js \ No newline at end of file diff --git a/.eslintignore b/.eslintignore index 81b9e250bd69..dee29d8e38d0 100644 --- a/.eslintignore +++ b/.eslintignore @@ -19,3 +19,4 @@ public/livechat/ !.scripts public/pdf.worker.min.js imports/client/ +!/.storybook/ diff --git a/.eslintrc b/.eslintrc index a8bdb5166000..46805bc5b654 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,104 @@ { - "extends": ["@rocket.chat/eslint-config"], - "parser": "babel-eslint", - "globals": { - "__meteor_bootstrap__" : false, - "__meteor_runtime_config__" : false, - "Assets" : false, - "chrome" : false + "extends": [ + "@rocket.chat/eslint-config" + ], + "parser": "babel-eslint", + "globals": { + "__meteor_bootstrap__": false, + "__meteor_runtime_config__": false, + "Assets": false, + "chrome": false, + "jscolor": false }, - "plugins": ["react"], - "rules": { - "jsx-quotes": ["error", "prefer-single"], + "plugins": [ + "react" + ], + "rules": { + "jsx-quotes": [ + "error", + "prefer-single" + ], "react/jsx-uses-react": "error", "react/jsx-uses-vars": "error", "react/jsx-no-undef": "error", - "react/jsx-fragments": ["error", "syntax"], + "react/jsx-fragments": [ + "error", + "syntax" + ], }, "settings": { "react": { "version": "detect", }, }, + "overrides": [ + { + "files": [ + "**/*.ts", + "**/*.tsx" + ], + "extends": [ + "@rocket.chat/eslint-config", + "plugin:@typescript-eslint/recommended", + "plugin:@typescript-eslint/eslint-recommended" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2018, + "warnOnUnsupportedTypeScriptVersion": false, + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "legacyDecorators": true + } + }, + "plugins": [ + "react", + "@typescript-eslint" + ], + "rules": { + "jsx-quotes": [ + "error", + "prefer-single" + ], + "react/jsx-uses-react": "error", + "react/jsx-uses-vars": "error", + "react/jsx-no-undef": "error", + "react/jsx-fragments": [ + "error", + "syntax" + ], + "@typescript-eslint/ban-ts-ignore": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/interface-name-prefix": [ + "error", + "always" + ] + }, + "env": { + "browser": true, + "commonjs": true, + "es6": true, + "node": true + }, + "settings": { + "import/resolver": { + "node": { + "extensions": [ + ".js", + ".ts", + ".tsx" + ] + } + }, + "react": { + "version": "detect" + } + } + } + ] } diff --git a/.github/history.json b/.github/history.json index 4a215c606e22..8d301c76dcaa 100644 --- a/.github/history.json +++ b/.github/history.json @@ -37299,6 +37299,1037 @@ ] } ] + }, + "2.4.0-rc.0": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16043", + "title": "Update NodeJS to 8.17.0", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15933", + "title": "[NEW] Invite links: share a link to invite users", + "userLogin": "pierre-lehnen-rc", + "milestone": "2.4.0", + "contributors": [ + "pierre-lehnen-rc", + "web-flow", + "rodrigok" + ] + }, + { + "pr": "15998", + "title": "Fix typo in Italian translation", + "userLogin": "iannuzzelli", + "milestone": "2.4.0", + "contributors": [ + "iannuzzelli" + ] + }, + { + "pr": "16037", + "title": "Update Meteor to 1.8.3", + "userLogin": "sampaiodiego", + "milestone": "2.4.0", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "16010", + "title": "[FIX] Importer: Variable name appearing instead of it's value", + "userLogin": "ashwaniYDV", + "milestone": "2.4.0", + "contributors": [ + "ashwaniYDV" + ] + }, + { + "pr": "15977", + "title": "[IMPROVE] Replace livechat:inquiry publication by REST and Streamer", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "renatobecker" + ] + }, + { + "pr": "16021", + "title": "[IMPROVE] Sorting on livechat analytics queries were wrong", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15650", + "title": "[IMPROVE] Replace fullUserData publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "15885", + "title": "[IMPROVE] Replace integrations and integrationHistory publications by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "15886", + "title": "Some performance improvements", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "15930", + "title": "[FIX]Add time format for latest message on the sidebar", + "userLogin": "ritwizsinha", + "contributors": [ + "ritwizsinha", + "ggazzo" + ] + }, + { + "pr": "15994", + "title": "[FIX] Admin Setting descriptions and Storybook", + "userLogin": "tassoevan", + "milestone": "2.4.0", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "16033", + "title": "[IMPROVE] Notify logged agents when their departments change", + "userLogin": "renatobecker", + "milestone": "2.4.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15901", + "title": "[IMPROVE] Replace fullEmojiData publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "15948", + "title": "[IMPROVE] Replace adminRooms publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "15926", + "title": "[IMPROVE] Replace webdavAccounts publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "15878", + "title": "[IMPROVE] Replace oauth publications by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "15956", + "title": "[IMPROVE] Replace userAutocomplete publication by REST", + "userLogin": "MarcosSpessatto", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "15908", + "title": "[IMPROVE] Replace discussionsOfARoom publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "16023", + "title": "[FIX] width of upload-progress-text", + "userLogin": "mariaeduardacunha", + "milestone": "2.4.0", + "contributors": [ + "mariaeduardacunha" + ] + }, + { + "pr": "15685", + "title": "[IMPROVE] Move 'Reply in Thread' button from menu to message actions", + "userLogin": "antkaz", + "milestone": "2.4.0", + "contributors": [ + "antkaz", + "ggazzo" + ] + }, + { + "pr": "15570", + "title": "Fixed Grammatical Mistakes.", + "userLogin": "breaking-let", + "milestone": "2.4.0", + "contributors": [ + "breaking-let" + ] + }, + { + "pr": "16020", + "title": "Upgrade limax to 2.0.0", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15907", + "title": "[IMPROVE] Replace customSounds publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "16018", + "title": "[FIX] Message list scrolling to bottom on reactions", + "userLogin": "ggazzo", + "milestone": "2.4.0", + "contributors": [ + "ggazzo", + "mariaeduardacunha" + ] + }, + { + "pr": "16004", + "title": "[IMPROVE] Replace stdout publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "15978", + "title": "[FIX] SAML logout error", + "userLogin": "sampaiodiego", + "milestone": "2.4.0", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "16016", + "title": "[FIX]Added Join button to Read Only rooms.", + "userLogin": "gabriellsh", + "milestone": "2.4.0", + "contributors": [ + "gabriellsh", + "MartinSchoeler" + ] + }, + { + "pr": "15942", + "title": "[IMPROVE] Replace fullUserStatusData publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "15916", + "title": "[IMPROVE] Replace userData subscriptions by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego" + ] + }, + { + "pr": "16013", + "title": "[FIX] z-index of new message button", + "userLogin": "mariaeduardacunha", + "contributors": [ + "mariaeduardacunha" + ] + }, + { + "pr": "16017", + "title": "[FIX] new message popup", + "userLogin": "mariaeduardacunha", + "milestone": "2.4.0", + "contributors": [ + "mariaeduardacunha" + ] + }, + { + "pr": "16012", + "title": "[FIX] Changed renderMessage priority, fixed Katex on/off setting", + "userLogin": "gabriellsh", + "contributors": [ + "gabriellsh" + ] + }, + { + "pr": "16009", + "title": "[FIX] Empty security section when 2fa is disabled", + "userLogin": "MartinSchoeler", + "milestone": "2.4.0", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "16006", + "title": "[FIX] Dropzone being stuck when dragging to thread", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "15910", + "title": "[IMPROVE] Replace roles publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15792", + "title": "[IMPROVE] Livechat realtime dashboard", + "userLogin": "MarcosSpessatto", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "16001", + "title": "[FIX] Fix sort livechat rooms", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15927", + "title": "[NEW] Logout other clients when changing password", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15991", + "title": "[FIX] Guest's name field missing when forwarding livechat rooms", + "userLogin": "renatobecker", + "milestone": "2.4.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15968", + "title": "[IMPROVE] Replace livechat:rooms publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15989", + "title": "Remove unnecessary cron starts", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15979", + "title": "Enable typescript lint", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15988", + "title": "LingoHub based on develop", + "userLogin": "engelgabriel", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "15928", + "title": "[NEW] Do not print emails in console on production mode", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15985", + "title": "[FIX] Error of bind environment on user data export", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15503", + "title": "[IMPROVE] Replace livechat:officeHour publication to REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15975", + "title": "[FIX] Incorrect translation key on Livechat Appearance template", + "userLogin": "ritwizsinha", + "milestone": "2.4.0", + "contributors": [ + "ritwizsinha" + ] + }, + { + "pr": "15970", + "title": "[IMPROVE] Replace forgotten livechat:departmentAgents subscriptions", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "renatobecker" + ] + }, + { + "pr": "15966", + "title": "[FIX] Livechat Widget version 1.3.0", + "userLogin": "renatobecker", + "milestone": "2.3.2", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15962", + "title": "Fix 'How it all started' link on README", + "userLogin": "zdumitru", + "contributors": [ + "zdumitru", + "web-flow" + ] + }, + { + "pr": "15957", + "title": "[FIX] Invalid Redirect URI on Custom OAuth", + "userLogin": "pierre-lehnen-rc", + "milestone": "2.3.2", + "contributors": [ + "pierre-lehnen-rc" + ] + }, + { + "pr": "15961", + "title": "Check package-lock consistency with package.json on CI", + "userLogin": "rodrigok", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "15873", + "title": "Meteor update to 1.8.2", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego", + "rodrigok" + ] + }, + { + "pr": "15918", + "title": "GitHub CI", + "userLogin": "rodrigok", + "contributors": [ + "rodrigok", + "web-flow" + ] + }, + { + "pr": "15944", + "title": "[IMPROVE] Replace livechat:managers publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15943", + "title": "[IMPROVE] Replace livechat:visitorHistory publication by REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "renatobecker" + ] + }, + { + "pr": "15903", + "title": "[FIX] Livechat build without NodeJS installed", + "userLogin": "localguru", + "contributors": [ + "localguru" + ] + }, + { + "pr": "15940", + "title": "Change migration number 169 <-> 170", + "userLogin": "sampaiodiego", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "15939", + "title": "LingoHub based on develop", + "userLogin": "engelgabriel", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "15612", + "title": "[IMPROVE] Replace livechat:queue subscription", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "renatobecker", + "web-flow" + ] + }, + { + "pr": "15935", + "title": "[IMPROVE] Add deprecate warning in some unused publications", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "15937", + "title": "[FIX] Admin menu not showing after renamed integration permissions", + "userLogin": "n-se", + "milestone": "2.3.1", + "contributors": [ + "n-se" + ] + }, + { + "pr": "15934", + "title": "[FIX] Administration UI issues", + "userLogin": "tassoevan", + "milestone": "2.3.1", + "contributors": [ + "tassoevan" + ] + }, + { + "pr": "15496", + "title": "[IMPROVE] Replace livechat:customFields to REST", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "sampaiodiego", + "web-flow", + "renatobecker" + ] + }, + { + "pr": "15919", + "title": "[FIX] Server crash on sync with no response", + "userLogin": "geekgonecrazy", + "contributors": [ + "geekgonecrazy", + "web-flow" + ] + }, + { + "pr": "15915", + "title": "[FIX] Livechat permissions being overwrite on server restart", + "userLogin": "renatobecker", + "milestone": "2.3.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15897", + "title": "[FIX] Livechat triggers not firing", + "userLogin": "renatobecker", + "milestone": "2.3.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15895", + "title": "[FIX] Auto load image user preference", + "userLogin": "ggazzo", + "milestone": "2.3.1", + "contributors": [ + "ggazzo", + "web-flow" + ] + }, + { + "pr": "15887", + "title": "[IMPROVE] Validate user identity on send message process", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.0", + "contributors": [ + "MarcosSpessatto", + "web-flow" + ] + }, + { + "pr": "15850", + "title": "[FIX] Don't throw an error when a message is prevented from apps engine", + "userLogin": "wreiske", + "contributors": [ + "wreiske", + "web-flow" + ] + }, + { + "pr": "15898", + "title": "[FIX] Default value of the Livechat WebhookUrl setting", + "userLogin": "renatobecker", + "milestone": "2.3.1", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15888", + "title": "[IMPROVE] Update ui for Roles field", + "userLogin": "antkaz", + "milestone": "2.4.0", + "contributors": [ + "antkaz" + ] + }, + { + "pr": "15837", + "title": "[NEW] Apps-Engine event for when a livechat room is closed", + "userLogin": "lolimay", + "milestone": "2.4.0", + "contributors": [ + "lolimay", + "renatobecker", + "d-gubert" + ] + }, + { + "pr": "15894", + "title": "[CHORE] Replace findOne with findOneById methods (Omnichannel)", + "userLogin": "renatobecker", + "milestone": "2.4.0", + "contributors": [ + "renatobecker" + ] + }, + { + "pr": "15841", + "title": "[FIX] Thread Replies in Search", + "userLogin": "MartinSchoeler", + "contributors": [ + "MartinSchoeler" + ] + }, + { + "pr": "15872", + "title": "Merge master into develop & Set version to 3.0.0-develop", + "userLogin": "sampaiodiego", + "contributors": [ + "rodrigok", + "web-flow", + "sampaiodiego" + ] + } + ] + }, + "2.4.0-rc.1": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16053", + "title": "Regression: Update components", + "userLogin": "tassoevan", + "milestone": "2.4.0", + "contributors": [ + "tassoevan" + ] + } + ] + }, + "2.4.0-rc.2": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16084", + "title": "Regression: Missing button to copy Invite links", + "userLogin": "ggazzo", + "milestone": "2.4.0", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "16062", + "title": "[FIX] Registration form was hidden when login form was disabled", + "userLogin": "rodrigok", + "milestone": "2.4.0", + "contributors": [ + "rodrigok" + ] + } + ] + }, + "2.4.0-rc.3": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "2.4.0": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "2.4.1": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16189", + "title": "[FIX] Enable apps change properties of the sender on the message as before", + "userLogin": "d-gubert", + "milestone": "2.4.1", + "contributors": [ + "d-gubert", + "sampaiodiego", + "web-flow" + ] + }, + { + "pr": "16171", + "title": "[FIX] Add missing password field back to administration area", + "userLogin": "rodrigok", + "milestone": "2.4.1", + "contributors": [ + "rodrigok", + "sampaiodiego" + ] + }, + { + "pr": "16139", + "title": "[FIX] JS errors on Administration page", + "userLogin": "mariaeduardacunha", + "milestone": "2.4.1", + "contributors": [ + "mariaeduardacunha" + ] + } + ] + }, + "2.3.3": { + "node_version": "8.15.1", + "npm_version": "6.9.0", + "mongo_versions": [], + "pull_requests": [ + { + "pr": "16171", + "title": "[FIX] Add missing password field back to administration area", + "userLogin": "rodrigok", + "milestone": "2.4.1", + "contributors": [ + "rodrigok", + "sampaiodiego" + ] + } + ] + }, + "2.4.2": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16147", + "title": "[FIX] Setup Wizard inputs and Admin Settings", + "userLogin": "tassoevan", + "milestone": "2.4.2", + "contributors": [ + "tassoevan", + "ggazzo" + ] + }, + { + "pr": "16253", + "title": "[FIX] Slack CSV User Importer", + "userLogin": "ggazzo", + "milestone": "2.4.2", + "contributors": [ + "ggazzo" + ] + }, + { + "pr": "16233", + "title": "[FIX] Integrations list without pagination and outgoing integration creation", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.2", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "16184", + "title": "[FIX] User stuck after reset password", + "userLogin": "sampaiodiego", + "milestone": "2.4.2", + "contributors": [ + "sampaiodiego" + ] + } + ] + }, + "2.4.3": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16347", + "title": "[FIX] Unknown error when sending message if 'Set a User Name to Alias in Message' setting is enabled", + "userLogin": "sampaiodiego", + "milestone": "2.4.3", + "contributors": [ + "sampaiodiego" + ] + }, + { + "pr": "16176", + "title": "[FIX] Invite links usage by channel owners/moderators", + "userLogin": "pierre-lehnen-rc", + "milestone": "2.4.3", + "contributors": [ + "pierre-lehnen-rc", + "web-flow", + "sampaiodiego" + ] + } + ] + }, + "2.4.4": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16377", + "title": "Release 2.4.4", + "userLogin": "sampaiodiego", + "contributors": [ + "rodrigok", + "sampaiodiego" + ] + }, + { + "pr": "16361", + "title": "Regression: Rate limiter was not working due to Meteor internal changes", + "userLogin": "rodrigok", + "milestone": "2.4.4", + "contributors": [ + "rodrigok" + ] + }, + { + "pr": "16362", + "title": "[FIX] App removal was moving logs to the trash collection", + "userLogin": "rodrigok", + "milestone": "2.4.4", + "contributors": [ + "rodrigok" + ] + } + ] + }, + "2.4.5": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [] + }, + "2.4.6": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16395", + "title": "Revert message properties validation", + "userLogin": "MarcosSpessatto", + "milestone": "3.0.0", + "contributors": [ + "MarcosSpessatto" + ] + }, + { + "pr": "16401", + "title": "Fix index creation for apps_logs collection", + "userLogin": "rodrigok", + "milestone": "3.0.0", + "contributors": [ + "rodrigok" + ] + } + ] + }, + "2.4.7": { + "node_version": "8.17.0", + "npm_version": "6.13.4", + "mongo_versions": [ + "3.4", + "3.6", + "4.0" + ], + "pull_requests": [ + { + "pr": "16433", + "title": "[FIX] Option to make a channel default", + "userLogin": "MarcosSpessatto", + "milestone": "2.4.7", + "contributors": [ + "MarcosSpessatto" + ] + } + ] } } } \ No newline at end of file diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 000000000000..22e25d56bc06 --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,397 @@ +name: Build and Test + +on: + release: + types: [published] + pull_request: + branches: '**' + push: + branches: + - develop + +env: + CI: true + MONGO_URL: mongodb://localhost:27017 + TOOL_NODE_FLAGS: --max_old_space_size=4096 + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Github Info + run: | + echo "GITHUB_ACTION: $GITHUB_ACTION" + echo "GITHUB_ACTOR: $GITHUB_ACTOR" + echo "GITHUB_REF: $GITHUB_REF" + echo "GITHUB_HEAD_REF: $GITHUB_HEAD_REF" + echo "GITHUB_BASE_REF: $GITHUB_BASE_REF" + echo "github.event_name: ${{ github.event_name }}" + cat $GITHUB_EVENT_PATH + + - name: Use Node.js 8.17.0 + uses: actions/setup-node@v1 + with: + node-version: "8.17.0" + + - uses: actions/checkout@v1 + + - name: check package-lock + run: | + npx package-lock-check + + - name: Cache node modules + id: cache-nodemodules + uses: actions/cache@v1 + with: + path: node_modules + key: ${{ runner.OS }}-node_modules-${{ hashFiles('**/package-lock.json') }} + + - name: Cache meteor local + uses: actions/cache@v1 + with: + path: ./.meteor/local + key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions') }} + + - name: Cache meteor + uses: actions/cache@v1 + with: + path: ~/.meteor + key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release') }} + + - name: Install Meteor + run: | + # Restore bin from cache + set +e + METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) + METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") + set -e + LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor + if [ -e $LAUNCHER ] + then + echo "Cached Meteor bin found, restoring it" + sudo cp "$LAUNCHER" "/usr/local/bin/meteor" + else + echo "No cached Meteor bin found." + fi + + # only install meteor if bin isn't found + command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + + - name: Versions + run: | + npm --versions + node -v + meteor --version + meteor npm --versions + meteor node -v + git version + + - name: npm install + if: steps.cache-nodemodules.outputs.cache-hit != 'true' + run: | + meteor npm install + + - run: npm run lint + + - name: Launch MongoDB + uses: wbari/start-mongoDB@v0.2 + with: + mongoDBVersion: "4.0" + + - run: npm run testunit + + # To reduce memory need during actual build, build the packages solely first + # - name: Build a Meteor cache + # run: | + # # to do this we can clear the main files and it build the rest + # echo "" > server/main.js + # echo "" > client/main.js + # sed -i.backup 's/rocketchat:livechat/#rocketchat:livechat/' .meteor/packages + # meteor build --server-only --debug --directory /tmp/build-temp + # git checkout -- server/main.js client/main.js .meteor/packages + + - name: Reset Meteor + if: startsWith(github.ref, 'refs/tags/') == 'true' || github.ref == 'refs/heads/develop' + run: | + meteor reset + + - name: Build Rocket.Chat From Pull Request + if: startsWith(github.ref, 'refs/pull/') == true + env: + METEOR_PROFILE: 1000 + run: | + meteor build --server-only --directory --debug /tmp/build-test + + - name: Build Rocket.Chat + if: startsWith(github.ref, 'refs/pull/') != true + run: | + meteor build --server-only --directory /tmp/build-test + + - name: Prepare build + run: | + mkdir /tmp/build/ + cd /tmp/build-test + tar czf /tmp/build/Rocket.Chat.tar.gz bundle + cd /tmp/build-test/bundle/programs/server + npm install + cd /tmp + tar czf Rocket.Chat.test.tar.gz ./build-test + + - name: Store build for tests + uses: actions/upload-artifact@v1 + with: + name: build-test + path: /tmp/Rocket.Chat.test.tar.gz + + - name: Store build + uses: actions/upload-artifact@v1 + with: + name: build + path: /tmp/build + + test: + runs-on: ubuntu-16.04 + needs: build + + strategy: + matrix: + node-version: ["8.17.0"] + mongodb-version: ["4.0"] + + steps: + - name: Launch MongoDB + uses: wbari/start-mongoDB@v0.2 + with: + mongoDBVersion: ${{ matrix.mongodb-version }} --noprealloc --smallfiles --replSet=rs0 + + - name: Restore build for tests + uses: actions/download-artifact@v1 + with: + name: build-test + path: /tmp + + - name: Decompress build + run: | + cd /tmp + tar xzf Rocket.Chat.test.tar.gz + cd - + + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + + - name: Setup Chrome + run: | + npm i chromedriver + + - name: Configure Replica Set + run: | + docker exec mongo mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' + docker exec mongo mongo --eval 'rs.status()' + + - uses: actions/checkout@v1 + + - name: Cache node modules + id: cache-nodemodules + uses: actions/cache@v1 + with: + path: node_modules + key: ${{ runner.OS }}-build-${{ hashFiles('**/package-lock.json') }} + + - name: NPM install + if: steps.cache-nodemodules.outputs.cache-hit != 'true' + run: | + npm install + + - name: Test + env: + TEST_MODE: "true" + MONGO_URL: mongodb://localhost:27017/rocketchat + MONGO_OPLOG_URL: mongodb://localhost:27017/local + run: | + for i in $(seq 1 5); do (docker exec mongo mongo rocketchat --eval 'db.dropDatabase()') && xvfb-run --auto-servernum npm test && s=0 && break || s=$? && sleep 1; done; (exit $s) + +# notification: +# runs-on: ubuntu-latest +# needs: test + +# steps: +# - name: Rocket.Chat Notification +# uses: RocketChat/Rocket.Chat.GitHub.Action.Notification@1.1.1 +# with: +# type: ${{ job.status }} +# job_name: '**Build and Test**' +# url: ${{ secrets.ROCKETCHAT_WEBHOOK }} +# commit: true +# token: ${{ secrets.GITHUB_TOKEN }} + + build-image-pr: + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + + steps: + - uses: actions/checkout@v1 + + - name: Cache node modules + id: cache-nodemodules + uses: actions/cache@v1 + with: + path: node_modules + key: ${{ runner.OS }}-node_modules-${{ hashFiles('**/package-lock.json') }} + + - name: Cache meteor local + uses: actions/cache@v1 + with: + path: ./.meteor/local + key: ${{ runner.OS }}-meteor_cache-${{ hashFiles('.meteor/versions') }} + + - name: Cache meteor + uses: actions/cache@v1 + with: + path: ~/.meteor + key: ${{ runner.OS }}-meteor-${{ hashFiles('.meteor/release') }} + + - name: Use Node.js 8.17.0 + uses: actions/setup-node@v1 + with: + node-version: "8.17.0" + + - name: Install Meteor + run: | + # Restore bin from cache + set +e + METEOR_SYMLINK_TARGET=$(readlink ~/.meteor/meteor) + METEOR_TOOL_DIRECTORY=$(dirname "$METEOR_SYMLINK_TARGET") + set -e + LAUNCHER=$HOME/.meteor/$METEOR_TOOL_DIRECTORY/scripts/admin/launch-meteor + if [ -e $LAUNCHER ] + then + echo "Cached Meteor bin found, restoring it" + sudo cp "$LAUNCHER" "/usr/local/bin/meteor" + else + echo "No cached Meteor bin found." + fi + + # only install meteor if bin isn't found + command -v meteor >/dev/null 2>&1 || curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh + + - name: Versions + run: | + npm --versions + node -v + meteor --version + meteor npm --versions + meteor node -v + git version + echo $GITHUB_REF + + - name: npm install + if: steps.cache-nodemodules.outputs.cache-hit != 'true' + run: | + meteor npm install + + # To reduce memory need during actual build, build the packages solely first + # - name: Build a Meteor cache + # run: | + # # to do this we can clear the main files and it build the rest + # echo "" > server/main.js + # echo "" > client/main.js + # sed -i.backup 's/rocketchat:livechat/#rocketchat:livechat/' .meteor/packages + # meteor build --server-only --debug --directory /tmp/build-temp + # git checkout -- server/main.js client/main.js .meteor/packages + + - name: Build Rocket.Chat + run: | + meteor build --server-only --directory /tmp/build-pr + + - name: Build Docker image for PRs + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: pr-${{ github.event.number }} + run: | + cd /tmp/build-pr + + export OWNER="${GITHUB_REPOSITORY%/*}" + + docker login docker.pkg.github.com -u "${GITHUB_ACTOR}" -p "${GITHUB_TOKEN}" + + cp $GITHUB_WORKSPACE/.docker/Dockerfile . + + export LOWERCASE_REPOSITORY=$(echo "$GITHUB_REPOSITORY" | tr "[:upper:]" "[:lower:]") + + export IMAGE_NAME="docker.pkg.github.com/${LOWERCASE_REPOSITORY}/rocket.chat:${VERSION}" + + echo "Build official Docker image ${IMAGE_NAME}" + + docker build -t $IMAGE_NAME . + docker push $IMAGE_NAME + + image-build: + runs-on: ubuntu-latest + needs: test + + strategy: + matrix: + release: ["official"] + + env: + IMAGE: "assistify/chat" + + steps: + - uses: actions/checkout@v1 + + - name: Restore build + uses: actions/download-artifact@v1 + with: + name: build + path: /tmp/build + + - name: Unpack build + env: + DOCKER_USER: ${{ secrets.DOCKER_USER }} + DOCKER_PASS: ${{ secrets.DOCKER_PASS }} + run: | + cd /tmp/build + tar xzf Rocket.Chat.tar.gz + rm Rocket.Chat.tar.gz + + export DOCKER_PATH="${GITHUB_WORKSPACE}/.docker" + if [[ '${{ matrix.release }}' = 'preview' ]]; then + export IMAGE="${IMAGE}.preview" + export DOCKER_PATH="${DOCKER_PATH}-mongo" + fi; + + echo "Build ${{ matrix.release }} Docker image" + cp ${DOCKER_PATH}/Dockerfile . + if [ -e ${DOCKER_PATH}/entrypoint.sh ]; then + cp ${DOCKER_PATH}/entrypoint.sh . + fi; + + docker login -u $DOCKER_USER -p $DOCKER_PASS + + - name: Build Docker image for tag + if: github.event_name == 'release' + run: | + cd /tmp/build + export CIRCLE_TAG="${GITHUB_REF#*tags/}" + + docker build -t ${IMAGE}:$CIRCLE_TAG . + docker push ${IMAGE}:$CIRCLE_TAG + + if echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$' ; then + export RELEASE="latest" + elif echo "$CIRCLE_TAG" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' ; then + export RELEASE="release-candidate" + fi + + docker tag ${IMAGE}:$CIRCLE_TAG ${IMAGE}:${RELEASE} + docker push ${IMAGE}:${RELEASE} + + - name: Build Docker image for develop + if: github.ref == 'refs/heads/develop' + run: | + cd /tmp/build + docker build -t ${IMAGE}:develop . + docker push ${IMAGE}:develop diff --git a/.gitignore b/.gitignore index 1d778eda57fe..af1cd3d52034 100644 --- a/.gitignore +++ b/.gitignore @@ -78,3 +78,4 @@ packages/rocketchat-i18n/i18n/livechat.* tests/end-to-end/temporary_staged_test .screenshots /private/livechat +/storybook-static diff --git a/.meteor/.finished-upgraders b/.meteor/.finished-upgraders index 8f397c7dad01..bc5b50f7cd47 100644 --- a/.meteor/.finished-upgraders +++ b/.meteor/.finished-upgraders @@ -17,3 +17,4 @@ notices-for-facebook-graph-api-2 1.4.3-split-account-service-packages 1.5-add-dynamic-import-package 1.7-split-underscore-from-meteor-base +1.8.3-split-jquery-from-blaze diff --git a/.meteor/packages b/.meteor/packages index 263feae546d3..78743cc07118 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -7,16 +7,17 @@ rocketchat:mongo-config accounts-facebook@1.3.2 accounts-github@1.4.2 -accounts-google@1.3.2 +accounts-google@1.3.3 accounts-meteor-developer@1.4.2 -accounts-password@1.5.1 +accounts-password@1.5.2 accounts-twitter@1.4.2 blaze-html-templates check@1.3.1 ddp-rate-limiter@1.0.7 ddp-common@1.4.0 dynamic-import@0.5.1 -ecmascript@0.12.4 +ecmascript@0.13.2 +typescript@3.7.1 ejson@1.1.0 email@1.2.3 fastclick@1.0.13 @@ -25,7 +26,7 @@ jquery@1.11.10 logging@1.1.20 meteor-base@1.4.0 mobile-experience@1.0.5 -mongo@1.6.2 +mongo@1.7.0 random@1.1.0 rate-limit@1.0.9 reactive-dict@1.3.0 @@ -35,7 +36,7 @@ service-configuration@1.0.11 session@1.2.0 shell-server@0.4.0 spacebars -standard-minifier-js@2.4.1 +standard-minifier-js@2.5.2 tracker@1.2.0 #rocketchat:google-natural-language @@ -57,7 +58,6 @@ jparker:gravatar kadira:blaze-layout kadira:flow-router keepnox:perfect-scrollbar -mizzao:autocomplete mizzao:timesync mrt:reactive-store mystor:device-detection @@ -77,10 +77,10 @@ littledata:synced-cron edgee:slingshot jalik:ufs-local@0.2.5 -accounts-base@1.4.3 +accounts-base@1.4.5 accounts-oauth@1.1.16 autoupdate@1.6.0 -babel-compiler@7.3.4 +babel-compiler@7.4.2 google-oauth@1.2.6 htmljs less @@ -92,7 +92,8 @@ raix:eventemitter routepolicy@1.1.0 sha@1.0.9 templating -webapp@1.7.3 +webapp@1.7.5 webapp-hashing@1.0.9 rocketchat:oauth2-server rocketchat:i18n +dandv:caret-position diff --git a/.meteor/release b/.meteor/release index 97064e19937b..bfccdc2c96b3 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@1.8.1 +METEOR@1.8.3 diff --git a/.meteor/versions b/.meteor/versions index 4f242ffbf31d..ccdee8133729 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,25 +1,25 @@ -accounts-base@1.4.4 +accounts-base@1.4.5 accounts-facebook@1.3.2 accounts-github@1.4.2 accounts-google@1.3.3 accounts-meteor-developer@1.4.2 accounts-oauth@1.1.16 -accounts-password@1.5.1 +accounts-password@1.5.2 accounts-twitter@1.4.2 aldeed:simple-schema@1.5.4 allow-deny@1.1.0 autoupdate@1.6.0 -babel-compiler@7.3.4 -babel-runtime@1.3.0 +babel-compiler@7.4.2 +babel-runtime@1.4.0 base64@1.0.12 binary-heap@1.0.11 -blaze@2.3.3 +blaze@2.3.4 blaze-html-templates@1.1.2 blaze-tools@1.0.10 boilerplate-generator@1.6.0 caching-compiler@1.2.1 caching-html-compiler@1.1.3 -callback-hook@1.1.0 +callback-hook@1.2.0 cfs:http-methods@0.0.32 check@1.3.1 coffeescript@1.0.17 @@ -34,12 +34,12 @@ deps@1.0.12 diff-sequence@1.1.1 dispatch:run-as-user@1.1.1 dynamic-import@0.5.1 -ecmascript@0.12.7 +ecmascript@0.13.2 ecmascript-runtime@0.7.0 -ecmascript-runtime-client@0.8.0 -ecmascript-runtime-server@0.7.1 +ecmascript-runtime-client@0.9.0 +ecmascript-runtime-server@0.8.0 edgee:slingshot@0.7.1 -ejson@1.1.0 +ejson@1.1.1 email@1.2.3 es5-shim@4.8.0 facebook-oauth@1.6.0 @@ -76,24 +76,23 @@ littledata:synced-cron@1.5.1 livedata@1.0.18 localstorage@1.2.0 logging@1.1.20 -matb33:collection-hooks@0.8.4 +matb33:collection-hooks@0.9.1 mdg:validation-error@0.5.1 meteor@1.9.3 meteor-base@1.4.0 meteor-developer-oauth@1.2.1 meteorhacks:inject-initial@1.0.4 meteorspark:util@0.2.0 -minifier-css@1.4.2 -minifier-js@2.4.1 +minifier-css@1.4.3 +minifier-js@2.5.1 minimongo@1.4.5 -mizzao:autocomplete@0.5.1 mizzao:timesync@0.3.4 mobile-experience@1.0.5 mobile-status-bar@1.0.14 modern-browsers@0.1.4 -modules@0.13.0 -modules-runtime@0.10.3 -mongo@1.6.3 +modules@0.14.0 +modules-runtime@0.11.0 +mongo@1.7.0 mongo-decimal@0.1.1 mongo-dev-server@1.1.0 mongo-id@1.0.7 @@ -103,13 +102,13 @@ mystor:device-detection@0.2.0 nimble:restivus@0.8.12 nooitaf:colors@1.1.2_1 npm-bcrypt@0.9.3 -npm-mongo@3.1.2 +npm-mongo@3.2.0 oauth@1.2.8 oauth1@1.2.2 oauth2@1.2.1 observe-sequence@1.0.16 ordered-dict@1.1.0 -ostrio:cookies@2.4.0 +ostrio:cookies@2.5.0 pauli:accounts-linkedin@5.0.0 pauli:linkedin-oauth@5.0.0 promise@0.11.2 @@ -128,7 +127,7 @@ rocketchat:livechat@0.0.1 rocketchat:mongo-config@0.0.1 rocketchat:oauth2-server@2.1.0 rocketchat:push@3.3.1 -rocketchat:streamer@1.0.2 +rocketchat:streamer@1.1.0 rocketchat:tap-i18n@1.9.1 rocketchat:version@1.0.0 routepolicy@1.1.0 @@ -141,7 +140,7 @@ socket-stream-client@0.2.2 spacebars@1.0.15 spacebars-compiler@1.1.3 srp@1.0.12 -standard-minifier-js@2.4.1 +standard-minifier-js@2.5.2 templating@1.3.2 templating-compiler@1.3.3 templating-runtime@1.3.2 @@ -150,8 +149,9 @@ tmeasday:check-npm-versions@0.3.2 todda00:friendly-slugs@0.6.0 tracker@1.2.0 twitter-oauth@1.2.0 +typescript@3.7.1 ui@1.0.13 underscore@1.0.10 url@1.2.0 -webapp@1.7.4 +webapp@1.7.5 webapp-hashing@1.0.9 diff --git a/.scripts/houstonMetadata.js b/.scripts/houstonMetadata.js new file mode 100644 index 000000000000..34de398bc857 --- /dev/null +++ b/.scripts/houstonMetadata.js @@ -0,0 +1,51 @@ +const getMongoVersion = async function({ version, git }) { + try { + const workflows = await git.show([`${ version }:.github/workflows/build_and_test.yml`]); + + const mongoMatch = workflows.match(/mongodb\-version: \[([^\]]+)\]/); + if (!mongoMatch) { + return []; + } + + return mongoMatch[1].replace(/"/g, '').replace(/ /g, '').split(','); + } catch (e) { + console.error(e); + } + return []; +}; + +const getNodeNpmVersions = async function({ version, git, request }) { + try { + const meteorRelease = await git.show([`${ version }:.meteor/release`]); + if (!/^METEOR@(\d+\.){1,2}\d/.test(meteorRelease)) { + return {}; + } + + const meteorVersion = meteorRelease.replace(/\n|\s/g, ''); + + const requestResult = await request(`https://raw.githubusercontent.com/meteor/meteor/release/${ meteorVersion }/scripts/build-dev-bundle-common.sh`); + + return { + node_version: requestResult.match(/NODE_VERSION=((?:\d+\.){2}\d)/m)[1], + npm_version: requestResult.match(/NPM_VERSION=((?:\d+\.){2}\d)/m)[1], + }; + } catch (e) { + console.error(e); + } + + return {}; +}; + +module.exports = async function({ version, git, request }) { + const mongo_versions = await getMongoVersion({ version, git }); + const { + node_version, + npm_version, + } = await getNodeNpmVersions({ version, git, request }); + + return { + node_version, + npm_version, + mongo_versions, + }; +}; diff --git a/.scripts/separateTesting.sh b/.scripts/separateTesting.sh index 43abdff24bf0..ba9858201905 100755 --- a/.scripts/separateTesting.sh +++ b/.scripts/separateTesting.sh @@ -4,7 +4,7 @@ rm -rf $tmpPath mkdir -p $tmpPath [ -z "$RETRY_TESTS" ] && RETRY_TESTS=1 -paths=("tests/end-to-end/ui/*.js" "tests/end-to-end/ui_smarti/*.js") +paths=("tests/end-to-end/api/*.js" "tests/end-to-end/ui/*.js" "tests/end-to-end/ui_smarti/*.js") for path in ${paths}; do for file in $path; do diff --git a/.scripts/set-version.js b/.scripts/set-version.js index 6524ce70017b..102f8e14cb97 100644 --- a/.scripts/set-version.js +++ b/.scripts/set-version.js @@ -13,7 +13,7 @@ let pkgJson = {}; try { pkgJson = require(path.resolve( // eslint-disable-line import/no-dynamic-require process.cwd(), - './package.json' + './package.json', )); } catch (err) { console.error('no root package.json found'); @@ -21,7 +21,6 @@ try { const files = [ './package.json', - './.travis/snap.sh', './.circleci/snap.sh', './.circleci/update-releases.sh', './.docker/Dockerfile', @@ -88,7 +87,7 @@ git.status() type: 'confirm', message: 'Commit files?', name: 'commit', - }]) + }]), ) .then((answers) => { if (!answers.commit) { diff --git a/.scripts/start-xvfb.sh b/.scripts/start-xvfb.sh deleted file mode 100755 index 70be0b2e6bd7..000000000000 --- a/.scripts/start-xvfb.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env bash - -if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then - sh -e /etc/init.d/xvfb start - sleep 3 -fi diff --git a/.scripts/start.js b/.scripts/start.js index 70844ab61c2b..deadb683a3ef 100644 --- a/.scripts/start.js +++ b/.scripts/start.js @@ -43,7 +43,7 @@ function startProcess(opts, callback) { const proc = spawn( opts.command, opts.params, - opts.options + opts.options, ); if (opts.waitForMessage) { diff --git a/.scripts/version.js b/.scripts/version.js index 461003c2d585..73fc12c46db4 100644 --- a/.scripts/version.js +++ b/.scripts/version.js @@ -5,7 +5,7 @@ let pkgJson = {}; try { pkgJson = require(path.resolve( // eslint-disable-line import/no-dynamic-require process.cwd(), - './package.json' + './package.json', )); } catch (err) { console.error('no root package.json found'); diff --git a/.snapcraft/resources/preparenode b/.snapcraft/resources/preparenode index a9f4807fed1f..9490792ba201 100755 --- a/.snapcraft/resources/preparenode +++ b/.snapcraft/resources/preparenode @@ -1,6 +1,6 @@ #!/bin/bash -node_version="v8.15.1" +node_version="v8.17.0" unamem="$(uname -m)" if [[ $unamem == *aarch64* ]]; then diff --git a/.storybook/config.js b/.storybook/config.js index 5de80993cf9f..95a8b682d7a6 100644 --- a/.storybook/config.js +++ b/.storybook/config.js @@ -1,11 +1,8 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs }from '@storybook/addon-knobs'; +import { withKnobs } from '@storybook/addon-knobs'; import { MINIMAL_VIEWPORTS, INITIAL_VIEWPORTS } from '@storybook/addon-viewport/dist/defaults'; import { addDecorator, addParameters, configure } from '@storybook/react'; -import React from 'react'; -import { ConnectionStatusProvider } from '../client/components/providers/ConnectionStatusProvider.mock'; -import { TranslationProvider } from '../client/components/providers/TranslationProvider.mock'; +import { rocketChatDecorator } from './mocks/decorators'; addParameters({ viewport: { @@ -15,35 +12,9 @@ addParameters({ }, defaultViewport: 'responsive', }, -}) - -addDecorator(function RocketChatDecorator(fn) { - const linkElement = document.getElementById('theme-styles') || document.createElement('link'); - if (linkElement.id !== 'theme-styles') { - require('../app/theme/client/main.css'); - require('../app/theme/client/vendor/fontello/css/fontello.css'); - require('../client/RocketChat.font.css'); - linkElement.setAttribute('id', 'theme-styles'); - linkElement.setAttribute('rel', 'stylesheet'); - linkElement.setAttribute('href', 'https://open.rocket.chat/theme.css'); - document.head.appendChild(linkElement); - } - - return - - -
-
- {fn()} -
- - ; }); +addDecorator(rocketChatDecorator); addDecorator(withKnobs); configure(require.context('../client', true, /\.stories\.js$/), module); diff --git a/.storybook/mocks/decorators.js b/.storybook/mocks/decorators.js new file mode 100644 index 000000000000..8bf073a3b108 --- /dev/null +++ b/.storybook/mocks/decorators.js @@ -0,0 +1,31 @@ +import React from 'react'; + +import { MeteorProviderMock } from './providers'; + +export const rocketChatDecorator = (fn) => { + const linkElement = document.getElementById('theme-styles') || document.createElement('link'); + if (linkElement.id !== 'theme-styles') { + require('../../app/theme/client/main.css'); + require('../../app/theme/client/vendor/fontello/css/fontello.css'); + require('../../client/rocketchat.font.css'); + linkElement.setAttribute('id', 'theme-styles'); + linkElement.setAttribute('rel', 'stylesheet'); + linkElement.setAttribute('href', 'https://open.rocket.chat/theme.css'); + document.head.appendChild(linkElement); + } + + // eslint-disable-next-line import/no-unresolved + const { default: icons } = require('!!raw-loader!../../private/public/icons.svg'); + + return + +
+
+ {fn()} +
+ ; +}; diff --git a/.storybook/empty.js b/.storybook/mocks/empty.js similarity index 100% rename from .storybook/empty.js rename to .storybook/mocks/empty.js diff --git a/.storybook/meteor.js b/.storybook/mocks/meteor.js similarity index 97% rename from .storybook/meteor.js rename to .storybook/mocks/meteor.js index 13f507c7ef32..e4a5761e50d2 100644 --- a/.storybook/meteor.js +++ b/.storybook/mocks/meteor.js @@ -27,7 +27,7 @@ export const Mongo = { find: () => ({ observe: () => {}, fetch: () => [], - }) + }), }), }; @@ -63,7 +63,7 @@ window.Blaze = Blaze; export const check = () => {}; export const FlowRouter = { - route: () => {} + route: () => {}, }; export const BlazeLayout = {}; diff --git a/.storybook/mocks/providers.js b/.storybook/mocks/providers.js new file mode 100644 index 000000000000..1cdd00b7dac1 --- /dev/null +++ b/.storybook/mocks/providers.js @@ -0,0 +1,67 @@ +import i18next from 'i18next'; +import React from 'react'; + +import { TranslationContext } from '../../client/contexts/TranslationContext'; + +let contextValue; + +const getContextValue = () => { + if (contextValue) { + return contextValue; + } + + i18next.init({ + fallbackLng: 'en', + defaultNS: 'project', + resources: { + en: { + project: require('../../packages/rocketchat-i18n/i18n/en.i18n.json'), + }, + }, + interpolation: { + prefix: '__', + suffix: '__', + }, + initImmediate: false, + }); + + const translate = (key, ...replaces) => { + if (typeof replaces[0] === 'object') { + const [options] = replaces; + return i18next.t(key, options); + } + + if (replaces.length === 0) { + return i18next.t(key); + } + + return i18next.t(key, { + postProcess: 'sprintf', + sprintf: replaces, + }); + }; + + translate.has = (key) => key && i18next.exists(key); + + contextValue = { + languages: [{ + name: 'English', + en: 'English', + key: 'en', + }], + language: 'en', + translate, + }; + + return contextValue; +}; + +function TranslationProviderMock({ children }) { + return ; +} + +export function MeteorProviderMock({ children }) { + return + {children} + ; +} diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js index b9d6efb821d6..03a6a91feede 100644 --- a/.storybook/webpack.config.js +++ b/.storybook/webpack.config.js @@ -33,12 +33,12 @@ module.exports = async ({ config }) => { config.plugins.push(new webpack.NormalModuleReplacementPlugin( /^meteor/, - require.resolve('./meteor.js'), + require.resolve('./mocks/meteor.js'), )); config.plugins.push(new webpack.NormalModuleReplacementPlugin( /\.\/server\/index.js/, - require.resolve('./empty.js'), + require.resolve('./mocks/empty.js'), )); config.mode = 'development'; diff --git a/.stylelintignore b/.stylelintignore index c62f04a77570..4fc5d8a09035 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -1,4 +1,4 @@ app/theme/client/vendor/fontello/css/fontello.css -packages/meteor-autocomplete/client/autocomplete.css +app/meteor-autocomplete/client/autocomplete.css app/katex/katex.min.css app/emoji-emojione/client/*.css diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 31f618fadec9..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,104 +0,0 @@ -language: node_js -services: -- docker -- mongodb -branches: - only: - - develop - - "/^\\d+\\.\\d+\\.\\d+(-rc\\.\\d+)?$/" -git: - depth: 1 -node_js: -- '8' -addons: - apt: - sources: - - google-chrome - - ubuntu-toolchain-r-test - packages: - - google-chrome-stable - - g++-4.8 - firefox: "latest" -before_cache: -- rm -rf $HOME/build/RocketChat/Rocket.Chat/.meteor/local/log -- rm -rf $HOME/build/RocketChat/Rocket.Chat/.meteor/local/run -- rm -rf $HOME/build/RocketChat/Rocket.Chat/.meteor/local/db -cache: - directories: - - "$HOME/node_modules" - - "$HOME/.meteor" - - "$HOME/.npm" - - "$HOME/.node-gyp" - - "$HOME/build/RocketChat/Rocket.Chat/node_modules" - - "$HOME/build/RocketChat/Rocket.Chat/.meteor/local" - - "$HOME/build/RocketChat/Rocket.Chat/packages/rocketchat-livechat/.npm" - - "$HOME/build/RocketChat/Rocket.Chat/packages/rocketchat-livechat/.app/node_modules" - - "$HOME/build/RocketChat/Rocket.Chat/packages/rocketchat-livechat/.app/.meteor/local" -before_install: -- if [ ! -e "$HOME/.meteor/meteor" ]; then curl https://install.meteor.com | sed s/--progress-bar/-sL/g | /bin/sh; fi -# Start X Virtual Frame Buffer for headless testing with real browsers -- .scripts/start-xvfb.sh -install: -- export PATH="$HOME/.meteor:$PATH" -before_script: -- if [[ $TRAVIS_TAG ]]; then meteor reset; fi -- echo "replication:" | sudo tee -a /etc/mongod.conf -- |- - echo " replSetName: \"rs0\"" | sudo tee -a /etc/mongod.conf -- sudo service mongod restart -- mkdir /tmp/build -- meteor --version -- travis_retry meteor npm install -- |- - mongo --eval 'rs.initiate({_id:"rs0", members: [{"_id":1, "host":"localhost:27017"}]})' -- meteor npm run lint -- meteor npm run testunit -- travis_retry meteor build --headless /tmp/build -- mkdir /tmp/build-test -- tar -xf /tmp/build/Rocket.Chat.tar.gz -C /tmp/build-test/ -- cd /tmp/build-test/bundle/programs/server -- npm install -- cd - -- mongo --eval 'rs.status()' -- mongo meteor --eval 'db.getCollectionNames()' -script: -- travis_retry npm test -- mongo meteor --eval 'db.dropDatabase()' -- unset MONGO_OPLOG_URL -- travis_retry npm test -before_deploy: -- source ".travis/setartname.sh" -- source ".travis/setdeploydir.sh" -- ".travis/setupsig.sh" -- ".travis/namefiles.sh" -deploy: - - provider: s3 - access_key_id: AKIAIKIA7H7D47KUHYCA - secret_access_key: "$ACCESSKEY" - bucket: download.rocket.chat - skip_cleanup: true - upload_dir: build - local_dir: "$ROCKET_DEPLOY_DIR" - on: - condition: "$TRAVIS_PULL_REQUEST=false" - all_branches: true - # - provider: releases - # api-key: "$GITHUB_TOKEN" - # file_glob: true - # file: build/* - # skip_cleanup: true - # on: - # tags: true - -after_deploy: -- ".travis/docker.sh" -- ".travis/update-releases.sh" -- ".travis/snap.sh" -env: - global: - - DISPLAY=:99.0 - - CXX=g++-4.8 - - secure: HrPOM5sBibYkMcf9aeQThYPCDiXeLkg0Xgv0HvH88/ku/gphDpNEjHNReHZM3cyfm9y3RhHpVdD+Zzy38S2goKyewRzpXJsuyerOYkjND0v3tivhs9CAX8PAUxj1U5zllTyH4bgW2ZwRtNnwnmtIM/JJlnySMpKVDqIZBpbhn3ph9bJ2J+BW3D3Jw8meQ1vCX8szIibyJK/5QX6HG2RBFXJGYoQ8DmR8jQv0aJQvT1Az5DO4yImk8tX4NP95qOc19Jywr1DsbaSBZeJ8lFJAmBpIGx7KAmUVCcxSxfbXGRhs2K4iEYb3rJ/dU6KiyPsKGUG4aYNGgbvcX0ZxX/BZ6ZU9ff0E4IIf43IxoN3ElrOqOFk5msJAXbrJEreINSzDqKOy8NFYtCQ49E2gwzfage4ZXkhFyx3wMPa5bzpr3ncsTceMjMVz03uL781X6NLuCkUmXv+n8K2MNhJU9Xinpdx1GRJm+0lXJspNNJ1ruHeJtls4epj4bmCwKmmZbFKPXqa5e8xVcMIkwt1LMiHduhE+WgKNHdOMhXrCcTxF62ybLlsHXmyLLJeNjTeKS8QG2XSoonClDAz/1R41I1DsMPblcgz9uvYCf7UtyftbhJ83bnJeEmOYQiwijLG0+QMq+B2+mmZan3Z7Hl7O53dnwuLxz7EO7EhQhY+CqHVgc6s= - - MONGO_OPLOG_URL: "mongodb://localhost:27017/local" - - MONGO_URL: "mongodb://localhost:27017/meteor" - - TEST_MODE: "true" diff --git a/.travis/docker.sh b/.travis/docker.sh deleted file mode 100755 index 29a7b1b0d8db..000000000000 --- a/.travis/docker.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -set -x -set -euvo pipefail -IFS=$'\n\t' - -CURL_URL="https://registry.hub.docker.com/u/rocketchat/rocket.chat/trigger/$PUSHTOKEN/" - -if [[ $TRAVIS_TAG ]] - then - CURL_DATA='{"source_type":"Tag","source_name":"'"$TRAVIS_TAG"'"}'; -else - CURL_DATA='{"source_type":"Branch","source_name":"'"$TRAVIS_BRANCH"'"}'; -fi - -curl -H "Content-Type: application/json" --data "$CURL_DATA" -X POST "$CURL_URL" diff --git a/.travis/namefiles.sh b/.travis/namefiles.sh deleted file mode 100755 index 4b497de93bf6..000000000000 --- a/.travis/namefiles.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -x -set -euvo pipefail -IFS=$'\n\t' - -FILENAME="$ROCKET_DEPLOY_DIR/rocket.chat-$ARTIFACT_NAME.tgz"; - -ln -s /tmp/build/Rocket.Chat.tar.gz "$FILENAME" -gpg --armor --detach-sign "$FILENAME" diff --git a/.travis/setartname.sh b/.travis/setartname.sh deleted file mode 100755 index 38253aac315f..000000000000 --- a/.travis/setartname.sh +++ /dev/null @@ -1,6 +0,0 @@ -if [[ $TRAVIS_TAG ]] - then - export ARTIFACT_NAME="$(meteor npm run version --silent)" -else - export ARTIFACT_NAME="$(meteor npm run version --silent).$TRAVIS_BUILD_NUMBER" -fi diff --git a/.travis/setdeploydir.sh b/.travis/setdeploydir.sh deleted file mode 100755 index 2c49e4a7027a..000000000000 --- a/.travis/setdeploydir.sh +++ /dev/null @@ -1,2 +0,0 @@ -export ROCKET_DEPLOY_DIR="/tmp/deploy" -mkdir -p $ROCKET_DEPLOY_DIR diff --git a/.travis/setupsig.sh b/.travis/setupsig.sh deleted file mode 100755 index c5c424635d06..000000000000 --- a/.travis/setupsig.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash -set -x -set -euvo pipefail -IFS=$'\n\t' - -cp .travis/sign.key.gpg /tmp -gpg --yes --batch --passphrase=$mypass /tmp/sign.key.gpg -gpg --allow-secret-key-import --import /tmp/sign.key -rm /tmp/sign.key diff --git a/.travis/sign.key.gpg b/.travis/sign.key.gpg deleted file mode 100644 index 488e275998d5..000000000000 Binary files a/.travis/sign.key.gpg and /dev/null differ diff --git a/.travis/snap.sh b/.travis/snap.sh deleted file mode 100755 index f548e5961d26..000000000000 --- a/.travis/snap.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -set -euvo pipefail -IFS=$'\n\t' - -# Add launchpad to known hosts -ssh-keyscan -t rsa -H git.launchpad.net > ~/.ssh/known_hosts - -git config user.name "CI Bot" -git config user.email "rocketchat.buildmaster@git.launchpad.net" - -# Determine the channel to push snap to. -if [[ $TRAVIS_TAG =~ ^[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+ ]]; then - CHANNEL=candidate - RC_VERSION=$TRAVIS_TAG -elif [[ $TRAVIS_TAG ]]; then - CHANNEL=stable - RC_VERSION=$TRAVIS_TAG -else - CHANNEL=edge - RC_VERSION=2.3.2 -fi - -echo "Preparing to trigger a snap release for $CHANNEL channel" - -cd $PWD/.snapcraft - -# Decrypt key -openssl aes-256-cbc -K $encrypted_f5c8ae370556_key -iv $encrypted_f5c8ae370556_iv -in launchpadkey.enc -out launchpadkey -d - -# Change permissions -chmod 0600 launchpadkey - -# We need some meta data so it'll actually commit. This could be useful to have for debugging later. -echo "Tag: $TRAVIS_TAG \r\nBranch: $TRAVIS_BRANCH\r\nBuild: $TRAVIS_BUILD_NUMBER\r\nCommit: $TRAVIS_COMMIT" > buildinfo - -# Clone launchpad repo for the channel down. -GIT_SSH_COMMAND="ssh -i launchpadkey" git clone -b $CHANNEL git+ssh://rocket.chat.buildmaster@git.launchpad.net/rocket.chat launchpad - -# Rarely will change, but just incase we copy it all -cp -r resources buildinfo launchpad/ -sed s/#{RC_VERSION}/$RC_VERSION/ snapcraft.yaml > launchpad/snapcraft.yaml - -cd launchpad -git add resources snapcraft.yaml buildinfo - -# Another place where basic meta data will live for at a glance info -git commit -m "Travis Build: $TRAVIS_BUILD_NUMBER Travis Commit: $TRAVIS_COMMIT" - -# Push up up to the branch of choice. -GIT_SSH_COMMAND="ssh -i ../launchpadkey" git push origin $CHANNEL - -# Clean up -cd .. -rm -rf launchpadkey launchpad diff --git a/.travis/update-releases.sh b/.travis/update-releases.sh deleted file mode 100755 index 658000b9b8a0..000000000000 --- a/.travis/update-releases.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -x -set -euvo pipefail -IFS=$'\n\t' - -CURL_URL="https://rocket.chat/releases/update" - -curl -X POST "$CURL_URL" diff --git a/HISTORY.md b/HISTORY.md index 4216268cd744..cd65fb376ac3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,4 +1,276 @@ +# 2.4.7 +`2020-02-03 · 1 🐛 · 1 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- Option to make a channel default ([#16433](https://github.com/RocketChat/Rocket.Chat/pull/16433)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) + +# 2.4.6 +`2020-01-31 · 2 🔍 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +
+🔍 Minor changes + +- Revert message properties validation ([#16395](https://github.com/RocketChat/Rocket.Chat/pull/16395)) +- Fix index creation for apps_logs collection ([#16401](https://github.com/RocketChat/Rocket.Chat/pull/16401)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- [@rodrigok](https://github.com/rodrigok) + +# 2.4.5 +`2020-01-29 · 1 🐛 ` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- Update Apps Engine to version 1.11.2 + +# 2.4.4 +`2020-01-29 · 1 🐛 · 2 🔍 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- App removal was moving logs to the trash collection ([#16362](https://github.com/RocketChat/Rocket.Chat/pull/16362)) + +
+🔍 Minor changes + +- Release 2.4.4 ([#16377](https://github.com/RocketChat/Rocket.Chat/pull/16377)) +- Regression: Rate limiter was not working due to Meteor internal changes ([#16361](https://github.com/RocketChat/Rocket.Chat/pull/16361)) + +
+ +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 2.4.3 +`2020-01-28 · 2 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- Unknown error when sending message if 'Set a User Name to Alias in Message' setting is enabled ([#16347](https://github.com/RocketChat/Rocket.Chat/pull/16347)) +- Invite links usage by channel owners/moderators ([#16176](https://github.com/RocketChat/Rocket.Chat/pull/16176)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 2.4.2 +`2020-01-17 · 4 🐛 · 4 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- Setup Wizard inputs and Admin Settings ([#16147](https://github.com/RocketChat/Rocket.Chat/pull/16147)) +- Slack CSV User Importer ([#16253](https://github.com/RocketChat/Rocket.Chat/pull/16253)) +- Integrations list without pagination and outgoing integration creation ([#16233](https://github.com/RocketChat/Rocket.Chat/pull/16233)) +- User stuck after reset password ([#16184](https://github.com/RocketChat/Rocket.Chat/pull/16184)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- [@ggazzo](https://github.com/ggazzo) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + +# 2.4.1 +`2020-01-10 · 2 🐛 · 3 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🐛 Bug fixes + +- Enable apps change properties of the sender on the message as before ([#16189](https://github.com/RocketChat/Rocket.Chat/pull/16189)) +- JS errors on Administration page ([#16139](https://github.com/RocketChat/Rocket.Chat/pull/16139)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@d-gubert](https://github.com/d-gubert) +- [@mariaeduardacunha](https://github.com/mariaeduardacunha) +- [@sampaiodiego](https://github.com/sampaiodiego) + +# 2.4.0 +`2019-12-27 · 4 🎉 · 28 🚀 · 21 🐛 · 19 🔍 · 21 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.17.0` +- NPM: `6.13.4` +- MongoDB: `3.4, 3.6, 4.0` + +### 🎉 New features + +- Invite links: share a link to invite users ([#15933](https://github.com/RocketChat/Rocket.Chat/pull/15933)) +- Logout other clients when changing password ([#15927](https://github.com/RocketChat/Rocket.Chat/pull/15927)) +- Do not print emails in console on production mode ([#15928](https://github.com/RocketChat/Rocket.Chat/pull/15928)) +- Apps-Engine event for when a livechat room is closed ([#15837](https://github.com/RocketChat/Rocket.Chat/pull/15837)) + +### 🚀 Improvements + +- Replace livechat:inquiry publication by REST and Streamer ([#15977](https://github.com/RocketChat/Rocket.Chat/pull/15977)) +- Sorting on livechat analytics queries were wrong ([#16021](https://github.com/RocketChat/Rocket.Chat/pull/16021)) +- Replace fullUserData publication by REST ([#15650](https://github.com/RocketChat/Rocket.Chat/pull/15650)) +- Replace integrations and integrationHistory publications by REST ([#15885](https://github.com/RocketChat/Rocket.Chat/pull/15885)) +- Notify logged agents when their departments change ([#16033](https://github.com/RocketChat/Rocket.Chat/pull/16033)) +- Replace fullEmojiData publication by REST ([#15901](https://github.com/RocketChat/Rocket.Chat/pull/15901)) +- Replace adminRooms publication by REST ([#15948](https://github.com/RocketChat/Rocket.Chat/pull/15948)) +- Replace webdavAccounts publication by REST ([#15926](https://github.com/RocketChat/Rocket.Chat/pull/15926)) +- Replace oauth publications by REST ([#15878](https://github.com/RocketChat/Rocket.Chat/pull/15878)) +- Replace userAutocomplete publication by REST ([#15956](https://github.com/RocketChat/Rocket.Chat/pull/15956)) +- Replace discussionsOfARoom publication by REST ([#15908](https://github.com/RocketChat/Rocket.Chat/pull/15908)) +- Move 'Reply in Thread' button from menu to message actions ([#15685](https://github.com/RocketChat/Rocket.Chat/pull/15685) by [@antkaz](https://github.com/antkaz)) +- Replace customSounds publication by REST ([#15907](https://github.com/RocketChat/Rocket.Chat/pull/15907)) +- Replace stdout publication by REST ([#16004](https://github.com/RocketChat/Rocket.Chat/pull/16004)) +- Replace fullUserStatusData publication by REST ([#15942](https://github.com/RocketChat/Rocket.Chat/pull/15942)) +- Replace userData subscriptions by REST ([#15916](https://github.com/RocketChat/Rocket.Chat/pull/15916)) +- Replace roles publication by REST ([#15910](https://github.com/RocketChat/Rocket.Chat/pull/15910)) +- Livechat realtime dashboard ([#15792](https://github.com/RocketChat/Rocket.Chat/pull/15792)) +- Replace livechat:rooms publication by REST ([#15968](https://github.com/RocketChat/Rocket.Chat/pull/15968)) +- Replace livechat:officeHour publication to REST ([#15503](https://github.com/RocketChat/Rocket.Chat/pull/15503)) +- Replace forgotten livechat:departmentAgents subscriptions ([#15970](https://github.com/RocketChat/Rocket.Chat/pull/15970)) +- Replace livechat:managers publication by REST ([#15944](https://github.com/RocketChat/Rocket.Chat/pull/15944)) +- Replace livechat:visitorHistory publication by REST ([#15943](https://github.com/RocketChat/Rocket.Chat/pull/15943)) +- Replace livechat:queue subscription ([#15612](https://github.com/RocketChat/Rocket.Chat/pull/15612)) +- Add deprecate warning in some unused publications ([#15935](https://github.com/RocketChat/Rocket.Chat/pull/15935)) +- Replace livechat:customFields to REST ([#15496](https://github.com/RocketChat/Rocket.Chat/pull/15496)) +- Validate user identity on send message process ([#15887](https://github.com/RocketChat/Rocket.Chat/pull/15887)) +- Update ui for Roles field ([#15888](https://github.com/RocketChat/Rocket.Chat/pull/15888) by [@antkaz](https://github.com/antkaz)) + +### 🐛 Bug fixes + +- Importer: Variable name appearing instead of it's value ([#16010](https://github.com/RocketChat/Rocket.Chat/pull/16010) by [@ashwaniYDV](https://github.com/ashwaniYDV)) +- Add time format for latest message on the sidebar ([#15930](https://github.com/RocketChat/Rocket.Chat/pull/15930) by [@ritwizsinha](https://github.com/ritwizsinha)) +- Admin Setting descriptions and Storybook ([#15994](https://github.com/RocketChat/Rocket.Chat/pull/15994)) +- width of upload-progress-text ([#16023](https://github.com/RocketChat/Rocket.Chat/pull/16023)) +- Message list scrolling to bottom on reactions ([#16018](https://github.com/RocketChat/Rocket.Chat/pull/16018)) +- SAML logout error ([#15978](https://github.com/RocketChat/Rocket.Chat/pull/15978)) +- Added Join button to Read Only rooms. ([#16016](https://github.com/RocketChat/Rocket.Chat/pull/16016)) +- z-index of new message button ([#16013](https://github.com/RocketChat/Rocket.Chat/pull/16013)) +- new message popup ([#16017](https://github.com/RocketChat/Rocket.Chat/pull/16017)) +- Changed renderMessage priority, fixed Katex on/off setting ([#16012](https://github.com/RocketChat/Rocket.Chat/pull/16012)) +- Empty security section when 2fa is disabled ([#16009](https://github.com/RocketChat/Rocket.Chat/pull/16009)) +- Dropzone being stuck when dragging to thread ([#16006](https://github.com/RocketChat/Rocket.Chat/pull/16006)) +- Fix sort livechat rooms ([#16001](https://github.com/RocketChat/Rocket.Chat/pull/16001)) +- Guest's name field missing when forwarding livechat rooms ([#15991](https://github.com/RocketChat/Rocket.Chat/pull/15991)) +- Error of bind environment on user data export ([#15985](https://github.com/RocketChat/Rocket.Chat/pull/15985)) +- Incorrect translation key on Livechat Appearance template ([#15975](https://github.com/RocketChat/Rocket.Chat/pull/15975) by [@ritwizsinha](https://github.com/ritwizsinha)) +- Livechat build without NodeJS installed ([#15903](https://github.com/RocketChat/Rocket.Chat/pull/15903) by [@localguru](https://github.com/localguru)) +- Server crash on sync with no response ([#15919](https://github.com/RocketChat/Rocket.Chat/pull/15919)) +- Don't throw an error when a message is prevented from apps engine ([#15850](https://github.com/RocketChat/Rocket.Chat/pull/15850) by [@wreiske](https://github.com/wreiske)) +- Thread Replies in Search ([#15841](https://github.com/RocketChat/Rocket.Chat/pull/15841)) +- Registration form was hidden when login form was disabled ([#16062](https://github.com/RocketChat/Rocket.Chat/pull/16062)) + +
+🔍 Minor changes + +- Update NodeJS to 8.17.0 ([#16043](https://github.com/RocketChat/Rocket.Chat/pull/16043)) +- Fix typo in Italian translation ([#15998](https://github.com/RocketChat/Rocket.Chat/pull/15998) by [@iannuzzelli](https://github.com/iannuzzelli)) +- Update Meteor to 1.8.3 ([#16037](https://github.com/RocketChat/Rocket.Chat/pull/16037)) +- Some performance improvements ([#15886](https://github.com/RocketChat/Rocket.Chat/pull/15886)) +- Fixed Grammatical Mistakes. ([#15570](https://github.com/RocketChat/Rocket.Chat/pull/15570) by [@breaking-let](https://github.com/breaking-let)) +- Upgrade limax to 2.0.0 ([#16020](https://github.com/RocketChat/Rocket.Chat/pull/16020)) +- Remove unnecessary cron starts ([#15989](https://github.com/RocketChat/Rocket.Chat/pull/15989)) +- Enable typescript lint ([#15979](https://github.com/RocketChat/Rocket.Chat/pull/15979)) +- LingoHub based on develop ([#15988](https://github.com/RocketChat/Rocket.Chat/pull/15988)) +- Fix 'How it all started' link on README ([#15962](https://github.com/RocketChat/Rocket.Chat/pull/15962) by [@zdumitru](https://github.com/zdumitru)) +- Check package-lock consistency with package.json on CI ([#15961](https://github.com/RocketChat/Rocket.Chat/pull/15961)) +- Meteor update to 1.8.2 ([#15873](https://github.com/RocketChat/Rocket.Chat/pull/15873)) +- GitHub CI ([#15918](https://github.com/RocketChat/Rocket.Chat/pull/15918)) +- Change migration number 169 <-> 170 ([#15940](https://github.com/RocketChat/Rocket.Chat/pull/15940)) +- LingoHub based on develop ([#15939](https://github.com/RocketChat/Rocket.Chat/pull/15939)) +- [CHORE] Replace findOne with findOneById methods (Omnichannel) ([#15894](https://github.com/RocketChat/Rocket.Chat/pull/15894)) +- Merge master into develop & Set version to 3.0.0-develop ([#15872](https://github.com/RocketChat/Rocket.Chat/pull/15872)) +- Regression: Update components ([#16053](https://github.com/RocketChat/Rocket.Chat/pull/16053)) +- Regression: Missing button to copy Invite links ([#16084](https://github.com/RocketChat/Rocket.Chat/pull/16084)) + +
+ +### 👩‍💻👨‍💻 Contributors 😍 + +- [@antkaz](https://github.com/antkaz) +- [@ashwaniYDV](https://github.com/ashwaniYDV) +- [@breaking-let](https://github.com/breaking-let) +- [@iannuzzelli](https://github.com/iannuzzelli) +- [@localguru](https://github.com/localguru) +- [@ritwizsinha](https://github.com/ritwizsinha) +- [@wreiske](https://github.com/wreiske) +- [@zdumitru](https://github.com/zdumitru) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@MarcosSpessatto](https://github.com/MarcosSpessatto) +- [@MartinSchoeler](https://github.com/MartinSchoeler) +- [@d-gubert](https://github.com/d-gubert) +- [@gabriellsh](https://github.com/gabriellsh) +- [@geekgonecrazy](https://github.com/geekgonecrazy) +- [@ggazzo](https://github.com/ggazzo) +- [@lolimay](https://github.com/lolimay) +- [@mariaeduardacunha](https://github.com/mariaeduardacunha) +- [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) +- [@renatobecker](https://github.com/renatobecker) +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) +- [@tassoevan](https://github.com/tassoevan) + +# 2.3.3 +`2020-01-10 · 1 🐛 · 2 👩‍💻👨‍💻` + +### Engine versions +- Node: `8.15.1` +- NPM: `6.9.0` +- MongoDB: `` + +### 🐛 Bug fixes + +- Add missing password field back to administration area ([#16171](https://github.com/RocketChat/Rocket.Chat/pull/16171)) + +### 👩‍💻👨‍💻 Core Team 🤓 + +- [@rodrigok](https://github.com/rodrigok) +- [@sampaiodiego](https://github.com/sampaiodiego) + # 2.3.2 `2019-12-12 · 2 🐛 · 2 👩‍💻👨‍💻` @@ -100,9 +372,9 @@ - Livechat transfer history messages ([#15780](https://github.com/RocketChat/Rocket.Chat/pull/15780)) - Add button to reset.css ([#15773](https://github.com/RocketChat/Rocket.Chat/pull/15773)) - Mentions before blockquote ([#15774](https://github.com/RocketChat/Rocket.Chat/pull/15774)) -- Sidebar font color was not respecting theming ([#15745](https://github.com/RocketChat/Rocket.Chat/pull/15745) by [@mariaeduardacunha](https://github.com/mariaeduardacunha)) +- Sidebar font color was not respecting theming ([#15745](https://github.com/RocketChat/Rocket.Chat/pull/15745)) - Add livechat agents into departments ([#15732](https://github.com/RocketChat/Rocket.Chat/pull/15732)) -- Changed cmsPage Style ([#15632](https://github.com/RocketChat/Rocket.Chat/pull/15632) by [@gabriellsh](https://github.com/gabriellsh)) +- Changed cmsPage Style ([#15632](https://github.com/RocketChat/Rocket.Chat/pull/15632)) - Forward Livechat UI and the related permissions ([#15718](https://github.com/RocketChat/Rocket.Chat/pull/15718)) - line-height to show entire letters ([#15581](https://github.com/RocketChat/Rocket.Chat/pull/15581) by [@nstseek](https://github.com/nstseek)) - Apply server side filters on Livechat lists ([#15717](https://github.com/RocketChat/Rocket.Chat/pull/15717)) @@ -110,7 +382,7 @@ - Livechat webhook broken when sending an image ([#15699](https://github.com/RocketChat/Rocket.Chat/pull/15699) by [@tatosjb](https://github.com/tatosjb)) - Sending messages to livechat rooms without a subscription ([#15707](https://github.com/RocketChat/Rocket.Chat/pull/15707)) - Duplicate label 'Hide Avatars' in accounts ([#15694](https://github.com/RocketChat/Rocket.Chat/pull/15694) by [@rajvaibhavdubey](https://github.com/rajvaibhavdubey)) -- Block Show_Setup_Wizard Option ([#15623](https://github.com/RocketChat/Rocket.Chat/pull/15623) by [@gabriellsh](https://github.com/gabriellsh)) +- Block Show_Setup_Wizard Option ([#15623](https://github.com/RocketChat/Rocket.Chat/pull/15623)) - Use Media Devices API to guess if a microphone is not available ([#15636](https://github.com/RocketChat/Rocket.Chat/pull/15636)) - Ignore file uploads from message box if text/plain content is being pasted ([#15631](https://github.com/RocketChat/Rocket.Chat/pull/15631)) - typo on PT-BR translation ([#15645](https://github.com/RocketChat/Rocket.Chat/pull/15645)) @@ -147,9 +419,7 @@ - [@Exordian](https://github.com/Exordian) - [@Montel](https://github.com/Montel) - [@antkaz](https://github.com/antkaz) -- [@gabriellsh](https://github.com/gabriellsh) - [@hmagarotto](https://github.com/hmagarotto) -- [@mariaeduardacunha](https://github.com/mariaeduardacunha) - [@mpdbl](https://github.com/mpdbl) - [@nstseek](https://github.com/nstseek) - [@oguhpereira](https://github.com/oguhpereira) @@ -164,9 +434,11 @@ - [@MartinSchoeler](https://github.com/MartinSchoeler) - [@d-gubert](https://github.com/d-gubert) - [@engelgabriel](https://github.com/engelgabriel) +- [@gabriellsh](https://github.com/gabriellsh) - [@geekgonecrazy](https://github.com/geekgonecrazy) - [@ggazzo](https://github.com/ggazzo) - [@mar-v](https://github.com/mar-v) +- [@mariaeduardacunha](https://github.com/mariaeduardacunha) - [@pierre-lehnen-rc](https://github.com/pierre-lehnen-rc) - [@renatobecker](https://github.com/renatobecker) - [@rodrigok](https://github.com/rodrigok) diff --git a/README.md b/README.md index e5c9bc73ed31..54ef952d36e1 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ Join thousands of members worldwide 24/7 in our [community server](https://open. [![Rocket.Chat](https://open.rocket.chat/api/v1/shield.svg?type=channel&name=Rocket.Chat&channel=dev)](https://open.rocket.chat/channel/dev) for developers needing help from the community to developing new features. -You can also join the conversation at [Twitter](https://twitter.com/RocketChat) and [Facebook](https://www.facebook.com/RocketChatApp). +You can also join the conversation on [Twitter](https://twitter.com/RocketChat) and [Facebook](https://www.facebook.com/RocketChatApp). # Desktop Apps Download the Native Cross-Platform Desktop Application at [Rocket.Chat.Electron](https://github.com/RocketChat/Rocket.Chat.Electron/releases) @@ -138,7 +138,7 @@ The easiest way to install a ready-to-run Rocket.Chat server on a Linux machine, [![DP deploy](https://raw.githubusercontent.com/DFabric/DPlatform-ShellCore/images/logo.png)](https://dfabric.github.io/DPlatform-ShellCore) ## IndieHosters -Get your Rocket.Chat instance hosted in a "as a Service" style. You register and we manage it for you! (updates, backup...). +Get your Rocket.Chat instance hosted in an "as a Service" style. You register and we manage it for you! (updates, backup...). [![Rocket.Chat on IndieHosters](https://indie.host/signup.png)](https://indiehosters.net/shop/product/rocket-chat-21) @@ -236,7 +236,7 @@ Run Rocket.Chat on your easy to use personal device. # About Rocket.Chat -Rocket.Chat is a Web Chat Server, developed in JavaScript, using the [Meteor](https://www.meteor.com/install) fullstack framework. +Rocket.Chat is a Web Chat Server, developed in JavaScript, using the [Meteor](https://www.meteor.com/install) full stack framework. It is a great solution for communities and companies wanting to privately host their own chat service or for developers looking forward to build and evolve their own chat platforms. @@ -321,12 +321,12 @@ It is a great solution for communities and companies wanting to privately host t ## Roadmap -To see an up to date view of what we have planned view our [milestones](https://github.com/RocketChat/Rocket.Chat/milestones). +To see an up to date view of what we have planned, view our [milestones](https://github.com/RocketChat/Rocket.Chat/milestones). ## How it all started -Read about [how it all started](https://blog.blackducksoftware.com/rocket-chat-enabling-privately-hosted-chat-services). +Read about [how it all started](https://www.synopsys.com/blogs/software-security/rocket-chat-privately-hosted-chat-services/). ## Awards [![InfoWorld Bossie Awards 2016 - Best Open Source Applications](https://raw.githubusercontent.com/Sing-Li/bbug/master/images/bossie.png)](http://www.infoworld.com/article/3122000/open-source-tools/bossie-awards-2016-the-best-open-source-applications.html#slide4) @@ -352,7 +352,7 @@ Please use the [Stack Overflow TAG](http://stackoverflow.com/questions/tagged/ro #### Hubot The docker image is ready. -Everyone can start hacking the adapter code, or launch his/her own bot within a few minutes now. +Everyone can start hacking the adapter code or launch his/her own bot within a few minutes now. Please head over to the [Hubot Integration Project](https://github.com/RocketChat/hubot-rocketchat) for more information. @@ -393,7 +393,7 @@ meteor npm install meteor npm start ``` -In order to debug the server part use [meteor debugging](https://docs.meteor.com/commandline.html#meteordebug). You should use Chrome for best debugging experience: +To debug the server part, use [meteor debugging](https://docs.meteor.com/commandline.html#meteordebug). You should use Chrome for best debugging experience: ```sh meteor debug @@ -416,7 +416,7 @@ If you want to help, send an email to support at rocket.chat to be invited to th ## How to Contribute -Already a JavaScript developer? Familiar with Meteor? [Pick an issue](https://github.com/RocketChat/Rocket.Chat/labels/contrib%3A%20easy), push a PR and instantly become a member of Rocket.Chat's international contributors community. For more information, check out our [Contributing Guide](.github/CONTRIBUTING.md) and our [Official Documentation for Contributors](https://rocket.chat/docs/contributing/). +Already a JavaScript developer? Familiar with Meteor? [Pick an issue](https://github.com/RocketChat/Rocket.Chat/labels/contrib%3A%20easy), push a PR and instantly become a member of Rocket.Chat's international contributors' community. For more information, check out our [Contributing Guide](.github/CONTRIBUTING.md) and our [Official Documentation for Contributors](https://rocket.chat/docs/contributing/). A lot of work has already gone into Rocket.Chat, but we have much bigger plans for it! @@ -434,7 +434,7 @@ Thanks to our core team [Marcelo Schmidt](https://github.com/marceloschmidt), [Rodrigo Nascimento](https://github.com/rodrigok), [Sing Li](https://github.com/Sing-Li), -and to hundreds of awesome [contributors](https://github.com/RocketChat/Rocket.Chat/graphs/contributors). +and hundreds of awesome [contributors](https://github.com/RocketChat/Rocket.Chat/graphs/contributors). ![JoyPixels](https://i.imgur.com/OrhYvLe.png) diff --git a/app/api/server/api.js b/app/api/server/api.js index 22e7e8c26d92..dd4125e36643 100644 --- a/app/api/server/api.js +++ b/app/api/server/api.js @@ -1,4 +1,5 @@ import { Meteor } from 'meteor/meteor'; +import { Random } from 'meteor/random'; import { DDPCommon } from 'meteor/ddp-common'; import { DDP } from 'meteor/ddp'; import { Accounts } from 'meteor/accounts-base'; @@ -312,6 +313,13 @@ export class APIClass extends Restivus { route: `${ this.request.route }${ this.request.method.toLowerCase() }`, }; let result; + + const connection = { + id: Random.id(), + close() {}, + token: this.token, + }; + try { api.enforceRateLimit(objectForRateLimitMatch, this.request, this.response); @@ -321,7 +329,18 @@ export class APIClass extends Restivus { }); } - result = originalAction.apply(this); + const invocation = new DDPCommon.MethodInvocation({ + connection, + isSimulation: false, + userId: this.userId, + }); + + Accounts._accountData[connection.id] = { + connection, + }; + Accounts._setAccountData(connection.id, 'loginToken', this.token); + + result = DDP._CurrentInvocation.withValue(invocation, () => originalAction.apply(this)); } catch (e) { logger.debug(`${ method } ${ route } threw an error:`, e.stack); @@ -331,6 +350,8 @@ export class APIClass extends Restivus { }[e.error] || 'failure'; result = API.v1[apiMethod](e.message, e.error); + } finally { + delete Accounts._accountData[connection.id]; } result = result || API.v1.success(); @@ -545,6 +566,8 @@ const getUserAuth = function _getUserAuth(...args) { token = Accounts._hashLoginToken(this.request.headers['x-auth-token']); } + this.token = token; + return { userId: this.request.headers['x-user-id'], token, diff --git a/app/api/server/index.js b/app/api/server/index.js index a45220f00a0f..be4ade59e8cf 100644 --- a/app/api/server/index.js +++ b/app/api/server/index.js @@ -20,6 +20,7 @@ import './v1/emoji-custom'; import './v1/groups'; import './v1/im'; import './v1/integrations'; +import './v1/invites'; import './v1/import'; import './v1/misc'; import './v1/permissions'; @@ -32,5 +33,9 @@ import './v1/subscriptions'; import './v1/users'; import './v1/video-conference'; import './v1/autotranslate'; +import './v1/webdav'; +import './v1/oauthapps'; +import './v1/custom-sounds'; +import './v1/custom-user-status'; export { API, APIClass, defaultRateLimiterOptions } from './api'; diff --git a/app/api/server/lib/custom-sounds.js b/app/api/server/lib/custom-sounds.js new file mode 100644 index 000000000000..0579e99f38e7 --- /dev/null +++ b/app/api/server/lib/custom-sounds.js @@ -0,0 +1,20 @@ +import { CustomSounds } from '../../../models/server/raw'; + +export async function findCustomSounds({ query = {}, pagination: { offset, count, sort } }) { + const cursor = await CustomSounds.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const sounds = await cursor.toArray(); + + return { + sounds, + count: sounds.length, + offset, + total, + }; +} diff --git a/app/api/server/lib/custom-user-status.js b/app/api/server/lib/custom-user-status.js new file mode 100644 index 000000000000..6108af3c6a72 --- /dev/null +++ b/app/api/server/lib/custom-user-status.js @@ -0,0 +1,20 @@ +import { CustomUserStatus } from '../../../models/server/raw'; + +export async function findCustomUserStatus({ query = {}, pagination: { offset, count, sort } }) { + const cursor = await CustomUserStatus.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const statuses = await cursor.toArray(); + + return { + statuses, + count: statuses.length, + offset, + total, + }; +} diff --git a/app/api/server/lib/emoji-custom.js b/app/api/server/lib/emoji-custom.js new file mode 100644 index 000000000000..1d7dde270664 --- /dev/null +++ b/app/api/server/lib/emoji-custom.js @@ -0,0 +1,20 @@ +import { EmojiCustom } from '../../../models/server/raw'; + +export async function findEmojisCustom({ query = {}, pagination: { offset, count, sort } }) { + const cursor = EmojiCustom.find(query, { + sort: sort || { name: 1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const emojis = await cursor.toArray(); + + return { + emojis, + count: emojis.length, + offset, + total, + }; +} diff --git a/app/api/server/lib/integrations.js b/app/api/server/lib/integrations.js new file mode 100644 index 000000000000..55db33a636a5 --- /dev/null +++ b/app/api/server/lib/integrations.js @@ -0,0 +1,27 @@ +import { Integrations } from '../../../models/server/raw'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; + +const hasIntegrationsPermission = async (userId, integration) => { + const type = integration.type === 'webhook-incoming' ? 'incoming' : 'outgoing'; + + if (await hasPermissionAsync(userId, `manage-${ type }-integrations`)) { + return true; + } + + if (userId === integration._createdBy._id) { + return hasPermissionAsync(userId, `manage-own-${ type }-integrations`); + } + + return false; +}; + +export const findOneIntegration = async ({ userId, integrationId, createdBy }) => { + const integration = await Integrations.findOneByIdAndCreatedByIfExists({ _id: integrationId, createdBy }); + if (!integration) { + throw new Error('The integration does not exists.'); + } + if (!await hasIntegrationsPermission(userId, integration)) { + throw new Error('not-authorized'); + } + return integration; +}; diff --git a/app/api/server/lib/messages.js b/app/api/server/lib/messages.js index 9fdd48b33e5b..18b0b71ca176 100644 --- a/app/api/server/lib/messages.js +++ b/app/api/server/lib/messages.js @@ -115,3 +115,28 @@ export async function findSnippetedMessages({ uid, roomId, pagination: { offset, total, }; } + +export async function findDiscussionsFromRoom({ uid, roomId, pagination: { offset, count, sort } }) { + const room = await Rooms.findOneById(roomId); + + if (!await canAccessRoomAsync(room, { _id: uid })) { + throw new Error('error-not-allowed'); + } + + const cursor = Messages.findDiscussionsByRoom(roomId, { + sort: sort || { ts: -1 }, + skip: offset, + limit: count, + }); + + const total = await cursor.count(); + + const messages = await cursor.toArray(); + + return { + messages, + count: messages.length, + offset, + total, + }; +} diff --git a/app/api/server/lib/oauthApps.js b/app/api/server/lib/oauthApps.js new file mode 100644 index 000000000000..f1312fc7101b --- /dev/null +++ b/app/api/server/lib/oauthApps.js @@ -0,0 +1,13 @@ +import { OAuthApps } from '../../../models/server/raw'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; + +export async function findOAuthApps({ uid }) { + if (!await hasPermissionAsync(uid, 'manage-oauth-apps')) { + throw new Error('error-not-allowed'); + } + return OAuthApps.find().toArray(); +} + +export async function findOneAuthApp({ clientId, appId }) { + return OAuthApps.findOneAuthAppByIdOrClientId({ clientId, appId }); +} diff --git a/app/api/server/lib/rooms.js b/app/api/server/lib/rooms.js new file mode 100644 index 000000000000..a0c371d1f59d --- /dev/null +++ b/app/api/server/lib/rooms.js @@ -0,0 +1,77 @@ +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; +import { Rooms } from '../../../models/server/raw'; + +export async function findAdminRooms({ uid, filter, types = [], pagination: { offset, count, sort } }) { + if (!await hasPermissionAsync(uid, 'view-room-administration')) { + throw new Error('error-not-authorized'); + } + const fields = { + prid: 1, + fname: 1, + name: 1, + t: 1, + cl: 1, + u: 1, + usernames: 1, + usersCount: 1, + muted: 1, + unmuted: 1, + ro: 1, + default: 1, + topic: 1, + msgs: 1, + archived: 1, + tokenpass: 1, + }; + + const name = filter && filter.trim(); + const discussion = types && types.includes('discussions'); + const showTypes = Array.isArray(types) ? types.filter((type) => type !== 'discussions') : []; + const options = { + fields, + sort: sort || { default: -1, name: 1 }, + skip: offset, + limit: count, + }; + + let cursor = Rooms.findByNameContaining(name, discussion, options); + + if (name && showTypes.length) { + cursor = Rooms.findByNameContainingAndTypes(name, showTypes, discussion, options); + } else if (showTypes.length) { + cursor = Rooms.findByTypes(showTypes, discussion, options); + } + + const total = await cursor.count(); + + const rooms = await cursor.toArray(); + + return { + rooms, + count: rooms.length, + offset, + total, + }; +} + +export async function findChannelAndPrivateAutocomplete({ uid, selector }) { + if (!await hasPermissionAsync(uid, 'view-other-user-channels')) { + return { items: [] }; + } + const options = { + fields: { + _id: 1, + name: 1, + }, + limit: 10, + sort: { + name: 1, + }, + }; + + const rooms = await Rooms.findChannelAndPrivateByNameStarting(selector.name, options).toArray(); + + return { + items: rooms, + }; +} diff --git a/app/api/server/lib/users.js b/app/api/server/lib/users.js new file mode 100644 index 000000000000..0742c9feab7d --- /dev/null +++ b/app/api/server/lib/users.js @@ -0,0 +1,27 @@ +import { Users } from '../../../models/server/raw'; +import { hasPermissionAsync } from '../../../authorization/server/functions/hasPermission'; + +export async function findUsersToAutocomplete({ uid, selector }) { + if (!await hasPermissionAsync(uid, 'view-outside-room')) { + return { items: [] }; + } + const exceptions = selector.exceptions || []; + const conditions = selector.conditions || {}; + const options = { + fields: { + name: 1, + username: 1, + status: 1, + }, + sort: { + username: 1, + }, + limit: 10, + }; + + const users = await Users.findActiveByUsernameOrNameRegexWithExceptionsAndConditions(selector.term, exceptions, conditions, options).toArray(); + + return { + items: users, + }; +} diff --git a/app/api/server/lib/webdav.js b/app/api/server/lib/webdav.js new file mode 100644 index 000000000000..cf5a3c8ea8f1 --- /dev/null +++ b/app/api/server/lib/webdav.js @@ -0,0 +1,14 @@ +import { WebdavAccounts } from '../../../models/server/raw'; + +export async function findWebdavAccountsByUserId({ uid }) { + return { + accounts: await WebdavAccounts.findWithUserId(uid, { + fields: { + _id: 1, + username: 1, + server_url: 1, + name: 1, + }, + }).toArray(), + }; +} diff --git a/app/api/server/v1/chat.js b/app/api/server/v1/chat.js index 4d1610a79d94..8e5ac669d229 100644 --- a/app/api/server/v1/chat.js +++ b/app/api/server/v1/chat.js @@ -9,7 +9,7 @@ import { API } from '../api'; import Rooms from '../../../models/server/models/Rooms'; import Users from '../../../models/server/models/Users'; import { settings } from '../../../settings'; -import { findMentionedMessages, findStarredMessages, findSnippetedMessageById, findSnippetedMessages } from '../lib/messages'; +import { findMentionedMessages, findStarredMessages, findSnippetedMessageById, findSnippetedMessages, findDiscussionsFromRoom } from '../lib/messages'; API.v1.addRoute('chat.delete', { authRequired: true }, { post() { @@ -680,3 +680,25 @@ API.v1.addRoute('chat.getSnippetedMessages', { authRequired: true }, { return API.v1.success(messages); }, }); + +API.v1.addRoute('chat.getDiscussions', { authRequired: true }, { + get() { + const { roomId } = this.queryParams; + const { sort } = this.parseJsonQuery(); + const { offset, count } = this.getPaginationItems(); + + if (!roomId) { + throw new Meteor.Error('error-invalid-params', 'The required "roomId" query param is missing.'); + } + const messages = Promise.await(findDiscussionsFromRoom({ + uid: this.userId, + roomId, + pagination: { + offset, + count, + sort, + }, + })); + return API.v1.success(messages); + }, +}); diff --git a/app/api/server/v1/custom-sounds.js b/app/api/server/v1/custom-sounds.js new file mode 100644 index 000000000000..76197b6cbaeb --- /dev/null +++ b/app/api/server/v1/custom-sounds.js @@ -0,0 +1,18 @@ +import { API } from '../api'; +import { findCustomSounds } from '../lib/custom-sounds'; + +API.v1.addRoute('custom-sounds.list', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, query } = this.parseJsonQuery(); + + return API.v1.success(Promise.await(findCustomSounds({ + query, + pagination: { + offset, + count, + sort, + }, + }))); + }, +}); diff --git a/app/api/server/v1/custom-user-status.js b/app/api/server/v1/custom-user-status.js new file mode 100644 index 000000000000..14764be6248f --- /dev/null +++ b/app/api/server/v1/custom-user-status.js @@ -0,0 +1,18 @@ +import { API } from '../api'; +import { findCustomUserStatus } from '../lib/custom-user-status'; + +API.v1.addRoute('custom-user-status.list', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, query } = this.parseJsonQuery(); + + return API.v1.success(Promise.await(findCustomUserStatus({ + query, + pagination: { + offset, + count, + sort, + }, + }))); + }, +}); diff --git a/app/api/server/v1/emoji-custom.js b/app/api/server/v1/emoji-custom.js index 15b4035dd2be..249331a3e832 100644 --- a/app/api/server/v1/emoji-custom.js +++ b/app/api/server/v1/emoji-custom.js @@ -3,6 +3,7 @@ import Busboy from 'busboy'; import { EmojiCustom } from '../../../models'; import { API } from '../api'; +import { findEmojisCustom } from '../lib/emoji-custom'; // DEPRECATED // Will be removed after v3.0.0 @@ -51,6 +52,22 @@ API.v1.addRoute('emoji-custom.list', { authRequired: true }, { }, }); +API.v1.addRoute('emoji-custom.all', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort, query } = this.parseJsonQuery(); + + return API.v1.success(Promise.await(findEmojisCustom({ + query, + pagination: { + offset, + count, + sort, + }, + }))); + }, +}); + API.v1.addRoute('emoji-custom.create', { authRequired: true }, { post() { Meteor.runAsUser(this.userId, () => { diff --git a/app/api/server/v1/integrations.js b/app/api/server/v1/integrations.js index a5d7d9ec2b41..8c71129a41e1 100644 --- a/app/api/server/v1/integrations.js +++ b/app/api/server/v1/integrations.js @@ -5,6 +5,7 @@ import { hasAtLeastOnePermission } from '../../../authorization/server'; import { IntegrationHistory, Integrations } from '../../../models'; import { API } from '../api'; import { mountIntegrationHistoryQueryBasedOnPermissions, mountIntegrationQueryBasedOnPermissions } from '../../../integrations/server/lib/mountQueriesBasedOnPermission'; +import { findOneIntegration } from '../lib/integrations'; API.v1.addRoute('integrations.create', { authRequired: true }, { post() { @@ -172,3 +173,20 @@ API.v1.addRoute('integrations.remove', { authRequired: true }, { } }, }); + +API.v1.addRoute('integrations.get', { authRequired: true }, { + get() { + const { integrationId, createdBy } = this.queryParams; + if (!integrationId) { + return API.v1.failure('The query parameter "integrationId" is required.'); + } + + return API.v1.success({ + integration: Promise.await(findOneIntegration({ + userId: this.userId, + integrationId, + createdBy, + })), + }); + }, +}); diff --git a/app/api/server/v1/invites.js b/app/api/server/v1/invites.js new file mode 100644 index 000000000000..513f99f7808e --- /dev/null +++ b/app/api/server/v1/invites.js @@ -0,0 +1,61 @@ +import { Meteor } from 'meteor/meteor'; + +import { API } from '../api'; +import { findOrCreateInvite } from '../../../invites/server/functions/findOrCreateInvite'; +import { removeInvite } from '../../../invites/server/functions/removeInvite'; +import { listInvites } from '../../../invites/server/functions/listInvites'; +import { useInviteToken } from '../../../invites/server/functions/useInviteToken'; +import { validateInviteToken } from '../../../invites/server/functions/validateInviteToken'; + +API.v1.addRoute('listInvites', { authRequired: true }, { + get() { + const result = listInvites(this.userId); + return API.v1.success(result); + }, +}); + +API.v1.addRoute('findOrCreateInvite', { authRequired: true }, { + post() { + const { rid, days, maxUses } = this.bodyParams; + const result = findOrCreateInvite(this.userId, { rid, days, maxUses }); + + return API.v1.success(result); + }, +}); + +API.v1.addRoute('removeInvite/:_id', { authRequired: true }, { + delete() { + const { _id } = this.urlParams; + const result = removeInvite(this.userId, { _id }); + + return API.v1.success(result); + }, +}); + +API.v1.addRoute('useInviteToken', { authRequired: true }, { + post() { + const { token } = this.bodyParams; + const result = useInviteToken(this.userId, token); + + return API.v1.success(result); + }, +}); + +API.v1.addRoute('validateInviteToken', { authRequired: false }, { + post() { + const { token } = this.bodyParams; + + if (!token) { + throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' }); + } + + let valid = true; + try { + validateInviteToken(token); + } catch (e) { + valid = false; + } + + return API.v1.success({ valid }); + }, +}); diff --git a/app/api/server/v1/misc.js b/app/api/server/v1/misc.js index 51892359ec20..258cdd9a70c3 100644 --- a/app/api/server/v1/misc.js +++ b/app/api/server/v1/misc.js @@ -3,13 +3,14 @@ import { check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import s from 'underscore.string'; -import { hasRole } from '../../../authorization'; -import { Info } from '../../../utils'; -import { Users } from '../../../models'; -import { settings } from '../../../settings'; +import { hasRole, hasPermission } from '../../../authorization/server'; +import { Info } from '../../../utils/server'; +import { Users } from '../../../models/server'; +import { settings } from '../../../settings/server'; import { API } from '../api'; import { getDefaultUserFields } from '../../../utils/server/functions/getDefaultUserFields'; import { getURL } from '../../../utils/lib/getURL'; +import { StdOut } from '../../../logger/server/publish'; // DEPRECATED @@ -162,7 +163,7 @@ API.v1.addRoute('spotlight', { authRequired: true }, { const { query } = this.queryParams; const result = Meteor.runAsUser(this.userId, () => - Meteor.call('spotlight', query) + Meteor.call('spotlight', query), ); return API.v1.success(result); @@ -202,3 +203,12 @@ API.v1.addRoute('directory', { authRequired: true }, { }); }, }); + +API.v1.addRoute('stdout.queue', { authRequired: true }, { + get() { + if (!hasPermission(this.userId, 'view-logs')) { + return API.v1.unauthorized(); + } + return API.v1.success({ queue: StdOut.queue }); + }, +}); diff --git a/app/api/server/v1/oauthapps.js b/app/api/server/v1/oauthapps.js new file mode 100644 index 000000000000..9cc40c33a59c --- /dev/null +++ b/app/api/server/v1/oauthapps.js @@ -0,0 +1,23 @@ +import { API } from '../api'; +import { findOAuthApps, findOneAuthApp } from '../lib/oauthApps'; + +API.v1.addRoute('oauth-apps.list', { authRequired: true }, { + get() { + return API.v1.success({ + oauthApps: Promise.await(findOAuthApps({ uid: this.userId })), + }); + }, +}); + +API.v1.addRoute('oauth-apps.get', { authRequired: true }, { + get() { + const { clientId, appId } = this.queryParams; + if (!clientId && !appId) { + return API.v1.failure('At least one of the query parameters "clientId" or "appId" is required.'); + } + + return API.v1.success({ + oauthApp: Promise.await(findOneAuthApp({ clientId, appId })), + }); + }, +}); diff --git a/app/api/server/v1/rooms.js b/app/api/server/v1/rooms.js index 92225c64fee0..85924d77d27f 100644 --- a/app/api/server/v1/rooms.js +++ b/app/api/server/v1/rooms.js @@ -4,6 +4,7 @@ import Busboy from 'busboy'; import { FileUpload } from '../../../file-upload'; import { Rooms, Messages } from '../../../models'; import { API } from '../api'; +import { findAdminRooms, findChannelAndPrivateAutocomplete } from '../lib/rooms'; function findRoomByIdOrName({ params, checkedArchived = true }) { if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) { @@ -134,8 +135,8 @@ API.v1.addRoute('rooms.saveNotification', { authRequired: true }, { const saveNotifications = (notifications, roomId) => { Object.keys(notifications).forEach((notificationKey) => Meteor.runAsUser(this.userId, () => - Meteor.call('saveNotificationSettings', roomId, notificationKey, notifications[notificationKey]) - ) + Meteor.call('saveNotificationSettings', roomId, notificationKey, notifications[notificationKey]), + ), ); }; const { roomId, notifications } = this.bodyParams; @@ -274,3 +275,36 @@ API.v1.addRoute('rooms.getDiscussions', { authRequired: true }, { }); }, }); + +API.v1.addRoute('rooms.adminRooms', { authRequired: true }, { + get() { + const { offset, count } = this.getPaginationItems(); + const { sort } = this.parseJsonQuery(); + const { types, filter } = this.requestParams(); + + return API.v1.success(Promise.await(findAdminRooms({ + uid: this.userId, + filter, + types, + pagination: { + offset, + count, + sort, + }, + }))); + }, +}); + +API.v1.addRoute('rooms.autocomplete.channelAndPrivate', { authRequired: true }, { + get() { + const { selector } = this.queryParams; + if (!selector) { + return API.v1.failure('The \'selector\' param is required'); + } + + return API.v1.success(Promise.await(findChannelAndPrivateAutocomplete({ + uid: this.userId, + selector: JSON.parse(selector), + }))); + }, +}); diff --git a/app/api/server/v1/subscriptions.js b/app/api/server/v1/subscriptions.js index 8ffa8711a335..236089ad1d62 100644 --- a/app/api/server/v1/subscriptions.js +++ b/app/api/server/v1/subscriptions.js @@ -62,7 +62,7 @@ API.v1.addRoute('subscriptions.read', { authRequired: true }, { }); Meteor.runAsUser(this.userId, () => - Meteor.call('readMessages', this.bodyParams.rid) + Meteor.call('readMessages', this.bodyParams.rid), ); return API.v1.success(); @@ -77,7 +77,7 @@ API.v1.addRoute('subscriptions.unread', { authRequired: true }, { } Meteor.runAsUser(this.userId, () => - Meteor.call('unreadMessages', firstUnreadMessage, roomId) + Meteor.call('unreadMessages', firstUnreadMessage, roomId), ); return API.v1.success(); diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index ec3a3090a47b..9110f09abee9 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -3,6 +3,7 @@ import { Match, check } from 'meteor/check'; import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; import _ from 'underscore'; import Busboy from 'busboy'; +import moment from 'moment'; import { Users, Subscriptions } from '../../../models/server'; import { hasPermission } from '../../../authorization'; @@ -16,9 +17,10 @@ import { setUserAvatar, saveCustomFields, } from '../../../lib'; -import { getFullUserData } from '../../../lib/server/functions/getFullUserData'; +import { getFullUserData, getFullUserDataById } from '../../../lib/server/functions/getFullUserData'; import { API } from '../api'; import { setStatusText } from '../../../lib/server'; +import { findUsersToAutocomplete } from '../lib/users'; API.v1.addRoute('users.create', { authRequired: true }, { post() { @@ -131,6 +133,42 @@ API.v1.addRoute('users.setActiveStatus', { authRequired: true }, { }, }); +API.v1.addRoute('users.deactivateIdle', { authRequired: true }, { + post() { + check(this.bodyParams, { + daysIdle: Match.Integer, + role: Match.Optional(String), + }); + + const { daysIdle, role } = this.bodyParams; + + if (!hasPermission(this.userId, 'edit-other-user-active-status')) { + return API.v1.unauthorized(); + } + + const lastLoggedIn = moment(new Date()).subtract(daysIdle, 'days'); + + const resultCursor = Users.findActiveNotLoggedInAfterWithRole(lastLoggedIn.toDate(), role || 'user', { + fields: { + _id: 1, + }, + }); + + // cache the count since it will be 0 after deactivation + const count = resultCursor.count(); + + Meteor.runAsUser(this.userId, () => { + resultCursor.forEach((user) => { + Meteor.call('setUserActiveStatus', user._id, false); + }); + }); + + return API.v1.success({ + count, + }); + }, +}); + API.v1.addRoute('users.getPresence', { authRequired: true }, { get() { if (this.isUserFromParams()) { @@ -152,14 +190,18 @@ API.v1.addRoute('users.getPresence', { authRequired: true }, { API.v1.addRoute('users.info', { authRequired: true }, { get() { - const { username } = this.getUserFromParams(); + const { username, userId } = this.requestParams(); const { fields } = this.parseJsonQuery(); - - const result = getFullUserData({ + const params = { userId: this.userId, filter: username, limit: 1, - }); + }; + + const result = userId + ? getFullUserDataById({ userId: this.userId, filterId: userId }) + : getFullUserData(params); + if (!result || result.count() !== 1) { return API.v1.failure(`Failed to get the user data for the userId of "${ this.userId }".`); } @@ -685,3 +727,27 @@ API.v1.addRoute('users.requestDataDownload', { authRequired: true }, { }); }, }); + +API.v1.addRoute('users.logoutOtherClients', { authRequired: true }, { + post() { + try { + Meteor.runAsUser(this.userId, () => API.v1.success(Meteor.call('logoutOtherClients'))); + } catch (error) { + return API.v1.failure(error); + } + }, +}); + +API.v1.addRoute('users.autocomplete', { authRequired: true }, { + get() { + const { selector } = this.queryParams; + if (!selector) { + return API.v1.failure('The \'selector\' param is required'); + } + + return API.v1.success(Promise.await(findUsersToAutocomplete({ + uid: this.userId, + selector: JSON.parse(selector), + }))); + }, +}); diff --git a/app/api/server/v1/webdav.js b/app/api/server/v1/webdav.js new file mode 100644 index 000000000000..33781eef1232 --- /dev/null +++ b/app/api/server/v1/webdav.js @@ -0,0 +1,8 @@ +import { API } from '../api'; +import { findWebdavAccountsByUserId } from '../lib/webdav'; + +API.v1.addRoute('webdav.getMyAccounts', { authRequired: true }, { + get() { + return API.v1.success(Promise.await(findWebdavAccountsByUserId({ uid: this.userId }))); + }, +}); diff --git a/app/apps/client/admin/appLogs.html b/app/apps/client/admin/appLogs.html index 47e901331c89..2200921d8f3a 100644 --- a/app/apps/client/admin/appLogs.html +++ b/app/apps/client/admin/appLogs.html @@ -31,6 +31,10 @@

+ {{#if log.instanceId}} + Instance: {{log.instanceId}} + {{/if}} + {{#each entry in log.entries}}
{{ entry.severity }}: {{ entry.timestamp }} (Caller: {{ entry.caller }})
diff --git a/app/apps/server/bridges/commands.js b/app/apps/server/bridges/commands.js index 9eefe1906117..aab50a25bc29 100644 --- a/app/apps/server/bridges/commands.js +++ b/app/apps/server/bridges/commands.js @@ -170,7 +170,7 @@ export class AppCommandsBridge { Object.freeze(user), Object.freeze(room), Object.freeze(params), - threadId + threadId, ); return Promise.await(this.orch.getManager().getCommandManager().getPreviews(command, context)); } @@ -185,7 +185,7 @@ export class AppCommandsBridge { Object.freeze(user), Object.freeze(room), Object.freeze(params), - threadId + threadId, ); Promise.await(this.orch.getManager().getCommandManager().executePreview(command, preview, context)); } diff --git a/app/apps/server/bridges/listeners.js b/app/apps/server/bridges/listeners.js index 26f0b7163089..01c039d62e64 100644 --- a/app/apps/server/bridges/listeners.js +++ b/app/apps/server/bridges/listeners.js @@ -36,4 +36,14 @@ export class AppListenerBridge { // this.orch.debugLog(e.stack); // } } + + async livechatEvent(inte, room) { + const rm = this.orch.getConverters().get('rooms').convertRoom(room); + const result = await this.orch.getManager().getListenerManager().executeListener(inte, rm); + + if (typeof result === 'boolean') { + return result; + } + return this.orch.getConverters().get('rooms').convertAppRoom(result); + } } diff --git a/app/apps/server/bridges/livechat.js b/app/apps/server/bridges/livechat.js index edd3aabf00be..5437246a0333 100644 --- a/app/apps/server/bridges/livechat.js +++ b/app/apps/server/bridges/livechat.js @@ -2,8 +2,9 @@ import { Random } from 'meteor/random'; import { getRoom } from '../../../livechat/server/api/lib/livechat'; import { Livechat } from '../../../livechat/server/lib/Livechat'; -import Rooms from '../../../models/server/models/Rooms'; +import LivechatRooms from '../../../models/server/models/LivechatRooms'; import LivechatVisitors from '../../../models/server/models/LivechatVisitors'; +import LivechatDepartment from '../../../models/server/models/LivechatDepartment'; import Users from '../../../models/server/models/Users'; export class AppLivechatBridge { @@ -11,6 +12,10 @@ export class AppLivechatBridge { this.orch = orch; } + isOnline() { + return Livechat.online(); + } + async createMessage(message, appId) { this.orch.debugLog(`The App ${ appId } is creating a new message.`); @@ -48,14 +53,19 @@ export class AppLivechatBridge { async createRoom(visitor, agent, appId) { this.orch.debugLog(`The App ${ appId } is creating a livechat room.`); - const agentUser = Users.findOneById(agent.id); - agentUser.agentId = agentUser._id; + let agentRoom; + if (agent && agent.id) { + const user = Users.getAgentInfo(agent.id); + agentRoom = Object.assign({}, { agentId: user._id }); + } - return this.orch.getConverters().get('rooms').convertRoom(getRoom({ + const result = await getRoom({ guest: this.orch.getConverters().get('visitors').convertAppVisitor(visitor), - agent: agentUser, + agent: agentRoom, rid: Random.id(), - }).room); + }); + + return this.orch.getConverters().get('rooms').convertRoom(result.room); } async closeRoom(room, comment, appId) { @@ -78,9 +88,9 @@ export class AppLivechatBridge { let result; if (departmentId) { - result = Rooms.findOpenByVisitorTokenAndDepartmentId(visitor.token, departmentId).fetch(); + result = LivechatRooms.findOpenByVisitorTokenAndDepartmentId(visitor.token, departmentId).fetch(); } else { - result = Rooms.findOpenByVisitorToken(visitor.token).fetch(); + result = LivechatRooms.findOpenByVisitorToken(visitor.token).fetch(); } return result.map((room) => this.orch.getConverters().get('rooms').convertRoom(room)); @@ -130,6 +140,40 @@ export class AppLivechatBridge { async findVisitors(query, appId) { this.orch.debugLog(`The App ${ appId } is looking for livechat visitors.`); + if (this.orch.isDebugging()) { + console.warn('The method AppLivechatBridge.findVisitors is deprecated. Please consider using its alternatives'); + } + return LivechatVisitors.find(query).fetch().map((visitor) => this.orch.getConverters().get('visitors').convertVisitor(visitor)); } + + async findVisitorById(id, appId) { + this.orch.debugLog(`The App ${ appId } is looking for livechat visitors.`); + + return this.orch.getConverters().get('visitors').convertById(id); + } + + async findVisitorByEmail(email, appId) { + this.orch.debugLog(`The App ${ appId } is looking for livechat visitors.`); + + return this.orch.getConverters().get('visitors').convertVisitor(LivechatVisitors.findOneGuestByEmailAddress(email)); + } + + async findVisitorByToken(token, appId) { + this.orch.debugLog(`The App ${ appId } is looking for livechat visitors.`); + + return this.orch.getConverters().get('visitors').convertVisitor(LivechatVisitors.getVisitorByToken(token)); + } + + async findVisitorByPhoneNumber(phoneNumber, appId) { + this.orch.debugLog(`The App ${ appId } is looking for livechat visitors.`); + + return this.orch.getConverters().get('visitors').convertVisitor(LivechatVisitors.findOneVisitorByPhone(phoneNumber)); + } + + async findDepartmentByIdOrName(value, appId) { + this.orch.debugLog(`The App ${ appId } is looking for livechat departments.`); + + return this.orch.getConverters().get('departments').convertDepartment(LivechatDepartment.findOneByIdOrName(value)); + } } diff --git a/app/apps/server/bridges/messages.js b/app/apps/server/bridges/messages.js index 062efcd64b8a..1071117d180e 100644 --- a/app/apps/server/bridges/messages.js +++ b/app/apps/server/bridges/messages.js @@ -1,9 +1,9 @@ -import { Meteor } from 'meteor/meteor'; import { Random } from 'meteor/random'; import { Messages, Users, Subscriptions } from '../../../models'; import { Notifications } from '../../../notifications'; import { updateMessage } from '../../../lib/server/functions/updateMessage'; +import { executeSendMessage } from '../../../lib/server/methods/sendMessage'; export class AppMessageBridge { constructor(orch) { @@ -13,13 +13,11 @@ export class AppMessageBridge { async create(message, appId) { this.orch.debugLog(`The App ${ appId } is creating a new message.`); - let msg = this.orch.getConverters().get('messages').convertAppMessage(message); + const convertedMessage = this.orch.getConverters().get('messages').convertAppMessage(message); - Meteor.runAsUser(msg.u._id, () => { - msg = Meteor.call('sendMessage', msg); - }); + const sentMessage = executeSendMessage(convertedMessage.u._id, convertedMessage); - return msg._id; + return sentMessage._id; } async getById(messageId, appId) { @@ -77,7 +75,7 @@ export class AppMessageBridge { Users.findByIds(users, { fields: { _id: 1 } }) .fetch() .forEach(({ _id }) => - Notifications.notifyUser(_id, 'message', rmsg) + Notifications.notifyUser(_id, 'message', rmsg), ); } } diff --git a/app/apps/server/converters/messages.js b/app/apps/server/converters/messages.js index aaac047a2ef2..9d7afc428941 100644 --- a/app/apps/server/converters/messages.js +++ b/app/apps/server/converters/messages.js @@ -135,6 +135,7 @@ export class AppMessagesConverter { attachments, reactions: message.reactions, parseUrls: message.parseUrls, + token: message.token, }; return Object.assign(newMessage, message._unmappedProperties_); diff --git a/app/apps/server/converters/visitors.js b/app/apps/server/converters/visitors.js index 66ab62158249..55151577b3fb 100644 --- a/app/apps/server/converters/visitors.js +++ b/app/apps/server/converters/visitors.js @@ -48,7 +48,8 @@ export class AppVisitorsConverter { name: visitor.name, token: visitor.token, phone: visitor.phone, - visitorEmails: visitor.visitorEmails, + ...visitor.visitorEmails && { visitorEmails: visitor.visitorEmails }, + ...visitor.department && { department: visitor.department }, }; return Object.assign(newVisitor, visitor._unmappedProperties_); diff --git a/app/apps/server/cron.js b/app/apps/server/cron.js index 0e51170734d9..3201612ea6b6 100644 --- a/app/apps/server/cron.js +++ b/app/apps/server/cron.js @@ -93,7 +93,7 @@ export const appsUpdateMarketplaceInfo = Meteor.bindEnvironment(function _appsUp Promise.await( Apps.updateAppsMarketplaceInfo(data) .then(notifyAdminsAboutInvalidApps) - .then(notifyAdminsAboutRenewedApps) + .then(notifyAdminsAboutRenewedApps), ); }); @@ -104,5 +104,3 @@ SyncedCron.add({ appsUpdateMarketplaceInfo(); }, }); - -SyncedCron.start(); diff --git a/app/apps/server/storage/logs-storage.js b/app/apps/server/storage/logs-storage.js index 78ef627ba599..005a69e70c8f 100644 --- a/app/apps/server/storage/logs-storage.js +++ b/app/apps/server/storage/logs-storage.js @@ -1,5 +1,6 @@ import { AppConsole } from '@rocket.chat/apps-engine/server/logging'; import { AppLogStorage } from '@rocket.chat/apps-engine/server/storage'; +import { InstanceStatus } from 'meteor/konecty:multiple-instances-status'; export class AppRealLogsStorage extends AppLogStorage { constructor(model) { @@ -25,6 +26,8 @@ export class AppRealLogsStorage extends AppLogStorage { return new Promise((resolve, reject) => { const item = AppConsole.toStorageEntry(appId, logger); + item.instanceId = InstanceStatus.id(); + try { const id = this.db.insert(item); diff --git a/app/assistify/ai/client/chatpalAdoption.js b/app/assistify/ai/client/chatpalAdoption.js deleted file mode 100644 index 8d3126eeef74..000000000000 --- a/app/assistify/ai/client/chatpalAdoption.js +++ /dev/null @@ -1,11 +0,0 @@ -// import { TAPi18n } from 'meteor/tap:i18n'; -// TAPi18n.loadTranslations({ -// en: { -// CHATPAL_ENTER_SEARCH_STRING: 'Search knowledge base', -// }, -// de: { -// CHATPAL_ENTER_SEARCH_STRING: 'Wissensbasis durchsuchen', -// }, -// }); - -// TODO: How to overwrite translations diff --git a/app/assistify/ai/client/index.js b/app/assistify/ai/client/index.js index 710ea45caeb5..841a5a0ae86a 100644 --- a/app/assistify/ai/client/index.js +++ b/app/assistify/ai/client/index.js @@ -4,7 +4,6 @@ import './tabbar.js'; import './models/MessagesExtension.js'; import '../public/stylesheets/smarti.css'; import './hooks/openAiTab.js'; -import './chatpalAdoption.js'; import './views/app/tabbar/smarti.html'; import './views/app/tabbar/smarti.js'; import './smartiLoader.js'; diff --git a/app/assistify/ai/client/tabbar.js b/app/assistify/ai/client/tabbar.js index 621ab0aadfd0..8ef404ea25e7 100755 --- a/app/assistify/ai/client/tabbar.js +++ b/app/assistify/ai/client/tabbar.js @@ -1,12 +1,28 @@ + +import { Tracker } from 'meteor/tracker'; + import { TabBar } from '../../../ui-utils/client'; +import { settings } from '../../../settings/client'; +// always remove the TabBar button displaying information from api.ai - we never need that TabBar.removeButton('external-search'); -TabBar.addButton({ - groups: ['channel', 'group', 'live'], - id: 'assistify-ai', - i18nTitle: 'Knowledge_Base', - icon: 'lightbulb', - template: 'AssistifySmarti', - order: 0, +Tracker.autorun(() => { + const enabled = settings.get('Assistify_AI_Enabled'); + if (enabled) { + TabBar.addButton({ + groups: ['channel', 'group', 'live'], + id: 'assistify-ai', + i18nTitle: 'Knowledge_Base', + icon: 'lightbulb', + template: 'AssistifySmarti', + order: 0, + }); + } else { + try { + TabBar.removeButton('assistify-ai'); + } catch (err) { + // may not exist, not an issue + } + } }); diff --git a/app/assistify/ai/package.js b/app/assistify/ai/package.js deleted file mode 100755 index 80cd05201343..000000000000 --- a/app/assistify/ai/package.js +++ /dev/null @@ -1,73 +0,0 @@ -// /* globals Npm */ -// Package.describe({ -// name: 'assistify:ai', -// version: '0.2.0', -// summary: 'Integration of artificial knowledge', -// git: 'http://github.com/assistify/Rocket.Chat', -// documentation: 'README.md', -// }); - -// function addDirectory(api, pathInPackage, environment) { -// const _ = Npm.require('underscore'); -// const fs = Npm.require('fs'); - -// const PACKAGE_PATH = 'packages/assistify-ai/'; -// const files = _.compact(_.map(fs.readdirSync(PACKAGE_PATH + pathInPackage), function(filename) { -// return `${ pathInPackage }/${ filename }`; -// })); -// api.addFiles(files, environment); -// } - -// Package.onUse(function(api) { -// api.use(['ecmascript', 'underscore']); -// api.use('templating', 'client'); -// api.use('meteorhacks:inject-initial'); // for provisioning of svg-icons - -// // Extensions to the RocketChat models -// api.addFiles('client/models/MessagesExtension.js', 'client'); -// api.addFiles('client/models/MessagesExtension.js', 'server'); - -// // Libraries -// api.addFiles('server/inject.js', 'server'); - -// // common components for client and server -// api.addFiles('models/AssistifySmarti.js', ['client', 'server']); - -// // Server business logic -// addDirectory(api, 'server/lib', 'server'); -// addDirectory(api, 'server/hooks', 'server'); -// addDirectory(api, 'server/methods', 'server'); - -// // Smarti proxy and router -// api.addFiles('server/SmartiProxy.js', 'server'); -// api.addFiles('server/SmartiRouter.js', 'server'); - -// // Configuration -// api.addFiles('config.js', 'server'); - -// // migration scripts -// api.addFiles('server/migrations.js', 'server'); - -// // Client business logic -// api.addFiles('client/tabbar.js', 'client'); -// api.addFiles('client/hooks/openAiTab.js', 'client'); -// api.addFiles('client/messageRenderer.js', 'client'); - -// // client views -// addDirectory(api, 'client/views/app/tabbar', 'client'); -// api.addFiles('client/smartiLoader.js', 'client'); - -// // styling -// api.addFiles('client/public/stylesheets/smarti.css', 'client'); - -// // Assets -// api.addAssets('client/public/assistify.png', 'client'); - -// // UI artifacts which are pre-processed or packaged by the server -// api.addAssets('client/public/icons.svg', 'server'); - -// // i18n in Rocket.Chat-package (packages/rocketchat-i18n/i18n - -// api.mainModule('assistify-ai.js', 'server'); - -// }); diff --git a/app/assistify/ai/server/index.js b/app/assistify/ai/server/index.js index dc57d58e660c..bbe95259af67 100644 --- a/app/assistify/ai/server/index.js +++ b/app/assistify/ai/server/index.js @@ -12,5 +12,4 @@ import './hooks/closeLivechatKnowledgeAdapter.js'; import './lib/SmartiAdapter.js'; import './lib/AiApiAdapter.js'; import './lib/KnowledgeAdapterProvider.js'; -import './migrations.js'; import '../../migrations/server/startup/migrations.js'; diff --git a/app/assistify/ai/server/lib/SmartiAdapter.js b/app/assistify/ai/server/lib/SmartiAdapter.js index 2e1cc7897ee9..f14db05cbd4a 100644 --- a/app/assistify/ai/server/lib/SmartiAdapter.js +++ b/app/assistify/ai/server/lib/SmartiAdapter.js @@ -614,7 +614,7 @@ export class SmartiAdapter { conversationId, }, { upsert: true, - } + }, ); } diff --git a/app/assistify/ai/server/methods/SmartiWidgetBackend.js b/app/assistify/ai/server/methods/SmartiWidgetBackend.js index 6f1cda903f47..6d150116ebd3 100644 --- a/app/assistify/ai/server/methods/SmartiWidgetBackend.js +++ b/app/assistify/ai/server/methods/SmartiWidgetBackend.js @@ -29,7 +29,7 @@ Meteor.methods({ userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(channelId); }, @@ -46,7 +46,7 @@ Meteor.methods({ userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(verbs.get, `conversation/${ conversationId }/analysis`, null, null, (error) => { // 404 is expected if no mapping exists if (error.response && error.response.statusCode === 404) { @@ -66,7 +66,7 @@ Meteor.methods({ userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(roomId); }, @@ -87,7 +87,7 @@ Meteor.methods({ userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(verbs.get, `conversation/${ conversationId }/analysis/template/${ templateIndex }/result/${ creator }`, { start, rows }); }, @@ -113,7 +113,7 @@ Meteor.methods({ userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(verbs.get, 'conversation/search', params); SystemLogger.debug('SearchResult: ', JSON.stringify(searchResult, null, 2)); return searchResult; @@ -139,7 +139,7 @@ function loadSmarti() { userId(userId) { return !hasPermission(userId, 'send-many-messages'); }, - } + }, )(verbs.get, 'plugin/v1/rocket.chat.js'); if (!script.error && script) { // add pseudo comment in order to make the script appear in the frontend as a file. This makes it de-buggable diff --git a/app/assistify/ai/server/migrations.js b/app/assistify/ai/server/migrations.js deleted file mode 100644 index 81ce7c129c9d..000000000000 --- a/app/assistify/ai/server/migrations.js +++ /dev/null @@ -1,5 +0,0 @@ -/* -Up to now, there's no "DB version" stored for assistify. -Until we've got expensive of contradicting migrations, we'll just use this file to write functions running -on startup which migrate data - ignoring the actual version - */ diff --git a/app/authorization/client/lib/streamer.js b/app/authorization/client/lib/streamer.js new file mode 100644 index 000000000000..1aab5af6eb45 --- /dev/null +++ b/app/authorization/client/lib/streamer.js @@ -0,0 +1,3 @@ +import { Meteor } from 'meteor/meteor'; + +export const rolesStreamer = new Meteor.Streamer('roles'); diff --git a/app/authorization/client/startup.js b/app/authorization/client/startup.js index 3656b7d00432..99c1f06e760d 100644 --- a/app/authorization/client/startup.js +++ b/app/authorization/client/startup.js @@ -3,9 +3,15 @@ import { Meteor } from 'meteor/meteor'; import { hasAtLeastOnePermission } from './hasPermission'; import { CachedCollectionManager } from '../../ui-cached-collection'; import { AdminBox } from '../../ui-utils/client/lib/AdminBox'; +import { APIClient } from '../../utils/client'; +import { Roles } from '../../models/client'; +import { rolesStreamer } from './lib/streamer'; Meteor.startup(() => { - CachedCollectionManager.onLogin(() => Meteor.subscribe('roles')); + CachedCollectionManager.onLogin(async () => { + const { roles } = await APIClient.v1.get('roles.list'); + roles.forEach((role) => Roles.insert(role)); + }); AdminBox.addOption({ href: 'admin-permissions', @@ -15,4 +21,12 @@ Meteor.startup(() => { return hasAtLeastOnePermission(['access-permissions', 'access-setting-permissions']); }, }); + const events = { + changed: (role) => { + delete role.type; + Roles.upsert({ _id: role.name }, role); + }, + removed: (role) => Roles.remove({ _id: role.name }), + }; + rolesStreamer.on('roles', (role) => events[role.type](role)); }); diff --git a/app/authorization/client/stylesheets/permissions.css b/app/authorization/client/stylesheets/permissions.css index 4b7532ccb87a..1561d2ebf536 100644 --- a/app/authorization/client/stylesheets/permissions.css +++ b/app/authorization/client/stylesheets/permissions.css @@ -120,7 +120,3 @@ } } } - -.page-container.permissions-manager { - overflow-y: auto !important; -} diff --git a/app/authorization/client/views/permissions.js b/app/authorization/client/views/permissions.js index d03ac7d52cbb..1ad319988a8e 100644 --- a/app/authorization/client/views/permissions.js +++ b/app/authorization/client/views/permissions.js @@ -8,13 +8,12 @@ import { Template } from 'meteor/templating'; import { Roles } from '../../../models'; import { ChatPermissions } from '../lib/ChatPermissions'; import { hasAllPermission } from '../hasPermission'; - -import { hasAtLeastOnePermission } from '..'; - import { t } from '../../../utils/client'; import { SideNav } from '../../../ui-utils/client/lib/SideNav'; import { CONSTANTS } from '../../lib'; +import { hasAtLeastOnePermission } from '..'; + Template.permissions.helpers({ tabsData() { const { @@ -85,7 +84,7 @@ Template.permissions.helpers({ _id: 1, }, limit, - } + }, ); }, @@ -105,7 +104,7 @@ Template.permissions.helpers({ group: 1, section: 1, }, - } + }, ); }, diff --git a/app/authorization/client/views/permissionsRole.js b/app/authorization/client/views/permissionsRole.js index 1788e47a2dd2..4e16cd666f54 100644 --- a/app/authorization/client/views/permissionsRole.js +++ b/app/authorization/client/views/permissionsRole.js @@ -95,7 +95,7 @@ Template.permissionsRole.helpers({ rules: [ { collection: 'CachedChannelList', - subscription: 'channelAndPrivateAutocomplete', + endpoint: 'rooms.autocomplete.channelAndPrivate', field: 'name', template: Template.roomSearch, noMatchTemplate: Template.roomSearchEmpty, @@ -118,7 +118,7 @@ Template.permissionsRole.helpers({ rules: [ { collection: 'CachedUserList', - subscription: 'userAutocomplete', + endpoint: 'users.autocomplete', field: 'username', template: Template.userSearch, noMatchTemplate: Template.userSearchEmpty, @@ -256,8 +256,6 @@ Template.permissionsRole.onCreated(async function() { this.searchRoom = new ReactiveVar(); this.searchUsername = new ReactiveVar(); this.usersInRole = new ReactiveVar([]); - - this.subscription = this.subscribe('roles', FlowRouter.getParam('name')); }); Template.permissionsRole.onRendered(function() { diff --git a/app/authorization/server/functions/canAccessRoom.js b/app/authorization/server/functions/canAccessRoom.js index ab4e59b457a7..abfa3e70c631 100644 --- a/app/authorization/server/functions/canAccessRoom.js +++ b/app/authorization/server/functions/canAccessRoom.js @@ -18,8 +18,8 @@ export const roomAccessValidators = [ return; } - const subscription = await Subscriptions.findOneByRoomIdAndUserId(room._id, user._id); - if (subscription) { + const exists = await Subscriptions.countByRoomIdAndUserId(room._id, user._id); + if (exists) { return true; } }, diff --git a/app/authorization/server/functions/hasRole.js b/app/authorization/server/functions/hasRole.js index 879aeaa01241..0159026d46fe 100644 --- a/app/authorization/server/functions/hasRole.js +++ b/app/authorization/server/functions/hasRole.js @@ -1,6 +1,8 @@ -import { Roles } from '../../../models'; +import { Roles } from '../../../models/server/raw'; -export const hasRole = (userId, roleNames, scope) => { +export const hasRoleAsync = async (userId, roleNames, scope) => { roleNames = [].concat(roleNames); return Roles.isUserInRoles(userId, roleNames, scope); }; + +export const hasRole = (userId, roleNames, scope) => Promise.await(hasRoleAsync(userId, roleNames, scope)); diff --git a/app/authorization/server/lib/streamer.js b/app/authorization/server/lib/streamer.js new file mode 100644 index 000000000000..f00103832be8 --- /dev/null +++ b/app/authorization/server/lib/streamer.js @@ -0,0 +1,5 @@ +import { Meteor } from 'meteor/meteor'; + +export const rolesStreamer = new Meteor.Streamer('roles'); +rolesStreamer.allowWrite('none'); +rolesStreamer.allowRead('logged'); diff --git a/app/authorization/server/methods/deleteRole.js b/app/authorization/server/methods/deleteRole.js index 8613e1761b0a..0b42263c23f8 100644 --- a/app/authorization/server/methods/deleteRole.js +++ b/app/authorization/server/methods/deleteRole.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import * as Models from '../../../models/server'; import { hasPermission } from '../functions/hasPermission'; +import { rolesStreamer } from '../lib/streamer'; Meteor.methods({ 'authorization:deleteRole'(roleName) { @@ -34,7 +35,13 @@ Meteor.methods({ method: 'authorization:deleteRole', }); } - - return Models.Roles.remove(role.name); + const removed = Models.Roles.remove(role.name); + if (removed) { + rolesStreamer.emit('roles', { + type: 'removed', + name: roleName, + }); + } + return removed; }, }); diff --git a/app/authorization/server/methods/saveRole.js b/app/authorization/server/methods/saveRole.js index 5f0998e7d493..64cb3437c9df 100644 --- a/app/authorization/server/methods/saveRole.js +++ b/app/authorization/server/methods/saveRole.js @@ -4,6 +4,7 @@ import { Roles } from '../../../models/server'; import { settings } from '../../../settings/server'; import { Notifications } from '../../../notifications/server'; import { hasPermission } from '../functions/hasPermission'; +import { rolesStreamer } from '../lib/streamer'; Meteor.methods({ 'authorization:saveRole'(roleData) { @@ -31,7 +32,10 @@ Meteor.methods({ _id: roleData.name, }); } - + rolesStreamer.emit('roles', { + type: 'changed', + ...roleData, + }); return update; }, }); diff --git a/app/authorization/server/publications/permissions/emitter.js b/app/authorization/server/publications/permissions/emitter.js index ef958ed48502..a7062439eb46 100644 --- a/app/authorization/server/publications/permissions/emitter.js +++ b/app/authorization/server/publications/permissions/emitter.js @@ -25,7 +25,7 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { Notifications.notifyLoggedInThisInstance( 'permissions-changed', clientAction, - data + data, ); if (data.level && data.level === CONSTANTS.SETTINGS_LEVEL) { @@ -36,7 +36,7 @@ Permissions.on('change', ({ clientAction, id, data, diff }) => { Notifications.notifyLoggedInThisInstance( 'private-settings-changed', 'updated', - setting + setting, ); } }); diff --git a/app/authorization/server/publications/permissions/index.js b/app/authorization/server/publications/permissions/index.js index 8e74f7b69325..09e7ccffc6c7 100644 --- a/app/authorization/server/publications/permissions/index.js +++ b/app/authorization/server/publications/permissions/index.js @@ -16,7 +16,7 @@ Meteor.methods({ remove: Permissions.trashFindDeletedAfter( updatedAt, {}, - { fields: { _id: 1, _deletedAt: 1 } } + { fields: { _id: 1, _deletedAt: 1 } }, ).fetch(), }; } diff --git a/app/authorization/server/publications/roles.js b/app/authorization/server/publications/roles.js index 57e17673eae8..c4ca8d926eb5 100644 --- a/app/authorization/server/publications/roles.js +++ b/app/authorization/server/publications/roles.js @@ -4,6 +4,7 @@ import { Roles } from '../../../models'; import { clearCache } from '../functions/hasPermission'; Meteor.publish('roles', function() { + console.warn('The publication "roles" is deprecated and will be removed after version v3.0.0'); if (!this.userId) { return this.ready(); } diff --git a/app/authorization/server/startup.js b/app/authorization/server/startup.js index ee430af1de25..3314bd586900 100644 --- a/app/authorization/server/startup.js +++ b/app/authorization/server/startup.js @@ -85,6 +85,7 @@ Meteor.startup(function() { { _id: 'view-outside-room', roles: ['admin', 'owner', 'moderator', 'user'] }, { _id: 'view-broadcast-member-list', roles: ['admin', 'owner', 'moderator'] }, { _id: 'call-management', roles: ['admin', 'owner', 'moderator'] }, + { _id: 'create-invite-links', roles: ['admin', 'owner', 'moderator'] }, { _id: 'view-l-room', roles: ['livechat-agent', 'livechat-manager', 'admin'] }, { _id: 'view-livechat-manager', roles: ['livechat-manager', 'admin'] }, { _id: 'view-livechat-rooms', roles: ['livechat-manager', 'admin'] }, @@ -148,6 +149,7 @@ Meteor.startup(function() { }); return previousSettingPermissions; }; + const createSettingPermission = function(setting, previousSettingPermissions) { const permissionId = getSettingPermissionId(setting._id); const permission = { diff --git a/app/autotranslate/server/models/Settings.js b/app/autotranslate/server/models/Settings.js deleted file mode 100644 index 3d90ad772527..000000000000 --- a/app/autotranslate/server/models/Settings.js +++ /dev/null @@ -1,21 +0,0 @@ -import { Settings } from '../../../models/server/models/Settings'; - -Object.assign(Settings, { - renameSetting(oldId, newId) { - const oldSetting = Settings.findById(oldId).fetch()[0]; - if (oldSetting) { - Settings.removeById(oldSetting._id); - - // there has been some problem with upsert() when changing the complete doc, so decide explicitly for insert or update - let newSetting = Settings.findById(newId).fetch()[0]; - if (newSetting) { - Settings.updateValueById(newId, oldSetting.value); - } else { - newSetting = oldSetting; - newSetting._id = newId; - delete newSetting.$loki; - Settings.insert(newSetting); - } - } - }, -}); diff --git a/app/autotranslate/server/msTranslate.js b/app/autotranslate/server/msTranslate.js index a299a4a927df..4e73f106b3ee 100644 --- a/app/autotranslate/server/msTranslate.js +++ b/app/autotranslate/server/msTranslate.js @@ -118,7 +118,7 @@ class MsAutoTranslate extends AutoTranslate { translations = Object.assign({}, ...targetLanguages.map((language) => ({ [language]: result.data.map((line) => line.translations.find((translation) => translation.to === language).text).join('\n'), - }) + }), )); } } catch (e) { @@ -160,7 +160,7 @@ class MsAutoTranslate extends AutoTranslate { translations = Object.assign({}, ...targetLanguages.map((language) => ({ [language]: result.data.map((line) => line.translations.find((translation) => translation.to === language).text).join('\n'), - }) + }), )); } } catch (e) { diff --git a/app/autotranslate/server/settings.js b/app/autotranslate/server/settings.js index 86294cb8e52b..e12e580453df 100644 --- a/app/autotranslate/server/settings.js +++ b/app/autotranslate/server/settings.js @@ -9,6 +9,7 @@ Meteor.startup(function() { section: 'AutoTranslate', public: true, }); + settings.add('AutoTranslate_ServiceProvider', 'google-translate', { type: 'select', group: 'Message', diff --git a/app/callbacks/lib/callbacks.js b/app/callbacks/lib/callbacks.js index efc44c0ee59d..d602e0384203 100644 --- a/app/callbacks/lib/callbacks.js +++ b/app/callbacks/lib/callbacks.js @@ -50,7 +50,7 @@ const createCallbackTimed = (hook, callbacks) => callbacks .map(wrapCallback) .map(handleResult) - .reduce(pipe, identity) + .reduce(pipe, identity), ); const create = (hook, cbs) => @@ -79,7 +79,7 @@ callbacks.add = function( hook, callback, priority = callbacks.priority.MEDIUM, - id = Random.id() + id = Random.id(), ) { callbacks[hook] = getHooks(hook); if (callbacks[hook].find((cb) => cb.id === id)) { diff --git a/app/cas/client/cas_client.js b/app/cas/client/cas_client.js index 7046f4d024ed..6cfa3c61d6f6 100644 --- a/app/cas/client/cas_client.js +++ b/app/cas/client/cas_client.js @@ -45,7 +45,7 @@ Meteor.loginWithCas = function(options, callback) { const popup = openCenteredPopup( loginUrl, popup_width || 800, - popup_height || 600 + popup_height || 600, ); diff --git a/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js b/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js index 71d0345a10f6..9b8660572c95 100644 --- a/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js +++ b/app/channel-settings-mail-messages/client/views/mailMessagesInstructions.js @@ -3,7 +3,6 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Blaze } from 'meteor/blaze'; import { Session } from 'meteor/session'; import { Template } from 'meteor/templating'; -import { AutoComplete } from 'meteor/mizzao:autocomplete'; import { Deps } from 'meteor/deps'; import toastr from 'toastr'; @@ -11,6 +10,7 @@ import { ChatRoom } from '../../../models'; import { t, isEmail, handleError, roomTypes } from '../../../utils'; import { settings } from '../../../settings'; import resetSelection from '../resetSelection'; +import { AutoComplete } from '../../../meteor-autocomplete/client'; const filterNames = (old) => { const reg = new RegExp(`^${ settings.get('UTF8_Names_Validation') }$`); @@ -39,7 +39,7 @@ Template.mailMessagesInstructions.helpers({ rules: [ { collection: 'CachedChannelList', - subscription: 'userAutocomplete', + endpoint: 'users.autocomplete', field: 'username', template: Template.userSearch, noMatchTemplate: Template.userSearchEmpty, @@ -253,7 +253,7 @@ Template.mailMessagesInstructions.onCreated(function() { rules: [ { collection: 'UserAndRoom', - subscription: 'userAutocomplete', + endpoint: 'users.autocomplete', field: 'username', matchAll: true, filter, diff --git a/app/channel-settings/client/views/channelSettings.html b/app/channel-settings/client/views/channelSettings.html index 9ff3a5886643..9d6f3b23f757 100644 --- a/app/channel-settings/client/views/channelSettings.html +++ b/app/channel-settings/client/views/channelSettings.html @@ -411,10 +411,10 @@
- {{#if canLeaveRoom}} - - {{/if}} {{#if canDeleteRoom}} {{/if}} diff --git a/app/channel-settings/client/views/channelSettings.js b/app/channel-settings/client/views/channelSettings.js index 41332e2aa06b..1d56f18ff3c0 100644 --- a/app/channel-settings/client/views/channelSettings.js +++ b/app/channel-settings/client/views/channelSettings.js @@ -412,7 +412,7 @@ Template.channelSettingsEditing.onCreated(function() { canView() { return roomTypes.roomTypes[room.t].allowRoomSettingChange( room, - RoomSettingsEnum.SYSTEM_MESSAGES + RoomSettingsEnum.SYSTEM_MESSAGES, ); }, getValue() { @@ -425,9 +425,9 @@ Template.channelSettingsEditing.onCreated(function() { return call('saveRoomSettings', room._id, 'systemMessages', value).then( () => { toastr.success( - t('System_messages_setting_changed_successfully') + t('System_messages_setting_changed_successfully'), ); - } + }, ); }, }, @@ -572,9 +572,9 @@ Template.channelSettingsEditing.onCreated(function() { return call('saveRoomSettings', room._id, 'retentionOverrideGlobal', value).then( () => { toastr.success( - t('Retention_setting_changed_successfully') + t('Retention_setting_changed_successfully'), ); - } + }, ); }, }, @@ -596,9 +596,9 @@ Template.channelSettingsEditing.onCreated(function() { return call('saveRoomSettings', room._id, 'retentionMaxAge', value).then( () => { toastr.success( - t('Retention_setting_changed_successfully') + t('Retention_setting_changed_successfully'), ); - } + }, ); }, }, @@ -620,9 +620,9 @@ Template.channelSettingsEditing.onCreated(function() { return call('saveRoomSettings', room._id, 'retentionExcludePinned', value).then( () => { toastr.success( - t('Retention_setting_changed_successfully') + t('Retention_setting_changed_successfully'), ); - } + }, ); }, }, @@ -644,9 +644,9 @@ Template.channelSettingsEditing.onCreated(function() { return call('saveRoomSettings', room._id, 'retentionFilesOnly', value).then( () => { toastr.success( - t('Retention_setting_changed_successfully') + t('Retention_setting_changed_successfully'), ); - } + }, ); }, }, @@ -664,7 +664,7 @@ Template.channelSettingsEditing.onCreated(function() { save(value) { return call('saveRoomSettings', room._id, 'encrypted', value).then(() => { toastr.success( - t('Encrypted_setting_changed_successfully') + t('Encrypted_setting_changed_successfully'), ); }); }, diff --git a/app/chatpal-search/server/provider/provider.js b/app/chatpal-search/server/provider/provider.js index a46d935c7250..18ee9a36f42b 100644 --- a/app/chatpal-search/server/provider/provider.js +++ b/app/chatpal-search/server/provider/provider.js @@ -315,7 +315,7 @@ class ChatpalProvider extends SearchProvider { payload.start || 0, payload.rows || this._settings.get('PageSize'), callback, - params + params, ); } @@ -332,7 +332,7 @@ class ChatpalProvider extends SearchProvider { this._settings.get('Main_Language'), this._getAcl(context), type, - callback + callback, ); } } diff --git a/app/cloud/server/functions/syncWorkspace.js b/app/cloud/server/functions/syncWorkspace.js index 7435ed4daa77..e4c7bf855424 100644 --- a/app/cloud/server/functions/syncWorkspace.js +++ b/app/cloud/server/functions/syncWorkspace.js @@ -72,7 +72,7 @@ export function syncWorkspace(reconnectCheck = false) { const { data } = result; - if (data.publicKey) { + if (data && data.publicKey) { Settings.updateValueById('Cloud_Workspace_PublicKey', data.publicKey); } diff --git a/app/crowd/server/crowd.js b/app/crowd/server/crowd.js index 5dc92542603d..ed64c9657884 100644 --- a/app/crowd/server/crowd.js +++ b/app/crowd/server/crowd.js @@ -322,7 +322,6 @@ const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounce crowd.sync(); }, }); - SyncedCron.start(); } }), 500); diff --git a/app/custom-sounds/client/admin/adminSounds.html b/app/custom-sounds/client/admin/adminSounds.html index 6fe002af89ce..b4ca2015025a 100644 --- a/app/custom-sounds/client/admin/adminSounds.html +++ b/app/custom-sounds/client/admin/adminSounds.html @@ -7,10 +7,10 @@
- {{#if isReady}} - {{> icon block="rc-input__icon-svg" icon="magnifier" }} - {{else}} + {{#if isLoading}} {{> loading }} + {{else}} + {{> icon block="rc-input__icon-svg" icon="magnifier" }} {{/if}}
- -
- {{>icon _id=_id icon="play" block="icon-play-circled"}} - {{>icon _id=_id icon="pause" block="icon-pause-circled"}} - {{>icon _id=_id icon="ban" block="icon-reset-circled"}} -
- + +
+ {{>icon _id=_id icon="play" block="icon-play-circled"}} + {{>icon _id=_id icon="pause" block="icon-pause-circled"}} + {{>icon _id=_id icon="ban" block="icon-reset-circled"}} +
+ {{else}} {{# with searchText}} @@ -58,11 +58,11 @@ {{/with}} {{/each}} - {{#unless isReady}} + {{#if isLoading}} {{> loading}} - {{/unless}} + {{/if}} {{/table}} {{/requiresPermission}} diff --git a/app/custom-sounds/client/admin/adminSounds.js b/app/custom-sounds/client/admin/adminSounds.js index 552bcd2ea42b..e142bc85d60d 100644 --- a/app/custom-sounds/client/admin/adminSounds.js +++ b/app/custom-sounds/client/admin/adminSounds.js @@ -2,40 +2,25 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Template } from 'meteor/templating'; -import s from 'underscore.string'; +import _ from 'underscore'; -import { CustomSounds as CustomSoundsModel } from '../../../models'; import { RocketChatTabBar, SideNav, TabBar } from '../../../ui-utils'; import { CustomSounds } from '../lib/CustomSounds'; +import { APIClient } from '../../../utils/client'; + +const LIST_SIZE = 50; +const DEBOUNCE_TIME_TO_SEARCH_IN_MS = 500; Template.adminSounds.helpers({ searchText() { const instance = Template.instance(); return instance.filter && instance.filter.get(); }, - isReady() { - if (Template.instance().ready != null) { - return Template.instance().ready.get(); - } - return undefined; - }, customsounds() { - return Template.instance().customsounds(); + return Template.instance().sounds.get(); }, isLoading() { - if (Template.instance().ready != null) { - if (!Template.instance().ready.get()) { - return 'btn-loading'; - } - } - }, - hasMore() { - if (Template.instance().limit != null) { - if (typeof Template.instance().customsounds === 'function') { - return Template.instance().limit.get() === Template.instance().customsounds().length; - } - } - return false; + return Template.instance().isLoading.get(); }, flexData() { return { @@ -47,18 +32,22 @@ Template.adminSounds.helpers({ onTableScroll() { const instance = Template.instance(); return function(currentTarget) { - if ( - currentTarget.offsetHeight + currentTarget.scrollTop - >= currentTarget.scrollHeight - 100 - ) { - return instance.limit.set(instance.limit.get() + 50); + if (currentTarget.offsetHeight + currentTarget.scrollTop < currentTarget.scrollHeight - 100) { + return; + } + const sounds = instance.sounds.get(); + if (instance.total.get() > sounds.length) { + instance.offset.set(instance.offset.get() + LIST_SIZE); } }; }, onTableItemClick() { const instance = Template.instance(); return function(item) { - instance.tabBarData.set(CustomSoundsModel.findOne({ _id: item._id })); + instance.tabBarData.set({ + sound: instance.sounds.get().find((sound) => sound._id === item._id), + onSuccess: instance.onSuccessCallback, + }); instance.tabBar.showGroup('custom-sounds-selected'); instance.tabBar.open('admin-sound-info'); }; @@ -67,9 +56,12 @@ Template.adminSounds.helpers({ Template.adminSounds.onCreated(function() { const instance = this; - this.limit = new ReactiveVar(50); + this.sounds = new ReactiveVar([]); + this.offset = new ReactiveVar(0); + this.total = new ReactiveVar(0); + this.query = new ReactiveVar({}); + this.isLoading = new ReactiveVar(false); this.filter = new ReactiveVar(''); - this.ready = new ReactiveVar(false); this.tabBar = new RocketChatTabBar(); this.tabBar.showGroup(FlowRouter.current().route.name); @@ -81,10 +73,6 @@ Template.adminSounds.onCreated(function() { i18nTitle: 'Custom_Sound_Add', icon: 'plus', template: 'adminSoundEdit', - openClick(/* e, t*/) { - instance.tabBarData.set(); - return true; - }, order: 1, }); @@ -97,33 +85,43 @@ Template.adminSounds.onCreated(function() { order: 2, }); - this.autorun(function() { - const limit = instance.limit != null ? instance.limit.get() : 0; - const subscription = instance.subscribe('customSounds', '', limit); - instance.ready.set(subscription.ready()); - }); + this.onSuccessCallback = () => { + this.offset.set(0); + return this.loadSounds(this.query.get(), this.offset.get()); + }; - this.customsounds = function() { - const filter = instance.filter != null ? s.trim(instance.filter.get()) : ''; + this.tabBarData.set({ + onSuccess: instance.onSuccessCallback, + }); - let query = {}; + this.loadSounds = _.debounce(async (query, offset) => { + this.isLoading.set(true); + const { sounds, total } = await APIClient.v1.get(`custom-sounds.list?count=${ LIST_SIZE }&offset=${ offset }&query=${ JSON.stringify(query) }`); + this.total.set(total); + if (offset === 0) { + this.sounds.set(sounds); + } else { + this.sounds.set(this.sounds.get().concat(sounds)); + } + this.isLoading.set(false); + }, DEBOUNCE_TIME_TO_SEARCH_IN_MS); + this.autorun(() => { + const filter = this.filter.get() && this.filter.get().trim(); + const offset = this.offset.get(); if (filter) { - const filterReg = new RegExp(s.escapeRegExp(filter), 'i'); - query = { name: filterReg }; + const regex = { $regex: filter, $options: 'i' }; + return this.loadSounds({ name: regex }, offset); } - - const limit = instance.limit != null ? instance.limit.get() : 0; - - return CustomSoundsModel.find(query, { limit, sort: { name: 1 } }).fetch(); - }; + return this.loadSounds({}, offset); + }); }); Template.adminSounds.onRendered(() => Tracker.afterFlush(function() { SideNav.setFlex('adminFlex'); SideNav.openFlex(); - }) + }), ); Template.adminSounds.events({ @@ -138,6 +136,7 @@ Template.adminSounds.events({ e.stopPropagation(); e.preventDefault(); t.filter.set(e.currentTarget.value); + t.offset.set(0); }, 'click .icon-play-circled'(e) { e.preventDefault(); diff --git a/app/custom-sounds/client/admin/route.js b/app/custom-sounds/client/admin/route.js index 5aebea1934f8..0150bef09169 100644 --- a/app/custom-sounds/client/admin/route.js +++ b/app/custom-sounds/client/admin/route.js @@ -1,12 +1,8 @@ -import { Meteor } from 'meteor/meteor'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { BlazeLayout } from 'meteor/kadira:blaze-layout'; FlowRouter.route('/admin/custom-sounds', { name: 'custom-sounds', - subscriptions(/* params, queryParams*/) { - this.register('customSounds', Meteor.subscribe('customSounds')); - }, async action(/* params*/) { await import('./views'); BlazeLayout.render('main', { center: 'adminSounds' }); diff --git a/app/custom-sounds/client/admin/soundEdit.js b/app/custom-sounds/client/admin/soundEdit.js index c55f2f5a03d3..cffe9c92d109 100644 --- a/app/custom-sounds/client/admin/soundEdit.js +++ b/app/custom-sounds/client/admin/soundEdit.js @@ -57,7 +57,7 @@ Template.soundEdit.onCreated(function() { this.sound = undefined; this.data.tabBar.showGroup('custom-sounds'); } - + this.onSuccess = Template.currentData().onSuccess; this.cancel = (form, name) => { form.reset(); this.data.tabBar.close(); @@ -133,7 +133,7 @@ Template.soundEdit.onCreated(function() { handleError(uploadError); console.log(uploadError); } - } + }, ); delete this.soundFile; toastr.success(TAPi18n.__('File_uploaded')); @@ -141,6 +141,7 @@ Template.soundEdit.onCreated(function() { } toastr.success(t('Custom_Sound_Saved_Successfully')); + this.onSuccess(); this.cancel(form, soundData.name); } diff --git a/app/custom-sounds/client/admin/soundInfo.js b/app/custom-sounds/client/admin/soundInfo.js index 4467e57e8e53..a27c74a03b2b 100644 --- a/app/custom-sounds/client/admin/soundInfo.js +++ b/app/custom-sounds/client/admin/soundInfo.js @@ -25,6 +25,7 @@ Template.soundInfo.helpers({ tabBar: instance.data.tabBar, data: instance.data.data, sound: instance.sound.get(), + onSuccess: instance.onSuccess, back(name) { instance.editingSound.set(); @@ -68,7 +69,7 @@ Template.soundInfo.events({ timer: 2000, showConfirmButton: false, }); - + instance.onSuccess(); instance.data.tabBar.showGroup('custom-sounds'); instance.data.tabBar.close(); } @@ -91,6 +92,7 @@ Template.soundInfo.onCreated(function() { this.editingSound = new ReactiveVar(); this.loadedName = new ReactiveVar(); + this.onSuccess = Template.currentData().onSuccess; this.autorun(() => { const data = Template.currentData(); @@ -100,7 +102,7 @@ Template.soundInfo.onCreated(function() { }); this.autorun(() => { - const data = Template.currentData(); + const data = Template.currentData().sound; const sound = this.sound.get(); if (sound && sound.name != null) { this.loadedName.set(sound.name); @@ -110,7 +112,7 @@ Template.soundInfo.onCreated(function() { }); this.autorun(() => { - const data = Template.currentData(); + const data = Template.currentData().sound; this.sound.set(data); }); }); diff --git a/app/custom-sounds/client/lib/CustomSounds.js b/app/custom-sounds/client/lib/CustomSounds.js index 52e1666c4d82..09c175ac6c04 100644 --- a/app/custom-sounds/client/lib/CustomSounds.js +++ b/app/custom-sounds/client/lib/CustomSounds.js @@ -20,7 +20,7 @@ class CustomSoundsClass { sound.src = this.getURL(sound); } const audio = $('