diff --git a/apps/meteor/app/authorization/server/functions/upsertPermissions.ts b/apps/meteor/app/authorization/server/functions/upsertPermissions.ts index 33ca8d1ec8e0..0bc66b4b9e7e 100644 --- a/apps/meteor/app/authorization/server/functions/upsertPermissions.ts +++ b/apps/meteor/app/authorization/server/functions/upsertPermissions.ts @@ -217,7 +217,6 @@ export const upsertPermissions = async (): Promise => { { _id: 'register-on-cloud', roles: ['admin'] }, { _id: 'test-admin-options', roles: ['admin'] }, { _id: 'sync-auth-services-users', roles: ['admin'] }, - { _id: 'manage-chatpal', roles: ['admin'] }, { _id: 'restart-server', roles: ['admin'] }, { _id: 'remove-slackbridge-links', roles: ['admin'] }, { _id: 'view-import-operations', roles: ['admin'] }, diff --git a/apps/meteor/app/chatpal-search/client/index.js b/apps/meteor/app/chatpal-search/client/index.js deleted file mode 100644 index 6bf6d22ad237..000000000000 --- a/apps/meteor/app/chatpal-search/client/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import './template/admin.html'; -import './template/result.html'; -import './template/suggestion.html'; -import './template/admin'; -import './template/result'; -import './template/suggestion'; -import './style.css'; diff --git a/apps/meteor/app/chatpal-search/client/style.css b/apps/meteor/app/chatpal-search/client/style.css deleted file mode 100644 index cc704ea30b0d..000000000000 --- a/apps/meteor/app/chatpal-search/client/style.css +++ /dev/null @@ -1,371 +0,0 @@ -.chatpal-admin-link { - text-decoration: underline !important; - - color: red !important; -} - -.chatpalProvider .search-form { - padding: 0 24px; -} - -.chatpal_search-loader { - position: relative; - - margin-top: 50px; -} - -.chatpal-search-container { - display: flex; - flex-direction: column; - flex: 1; -} - -.chatpal-search-result { - position: relative; - - overflow: auto; - overflow-x: hidden; - - flex: 1; - - padding: 0 24px 24px; - - background-color: #f1f1f1; -} - -.chatpal-search-typefilter { - display: flex; -} - -.chatpal-search-typefilter li { - display: flex; - flex: 0 0 33%; - - height: 35px; - padding-top: 10px; - - cursor: pointer; - - border-bottom: 4px solid white; - justify-content: center; -} - -.chatpal-search-typefilter li.selected { - border-bottom: 4px solid #125074; -} - -.chatpal-admin-header { - margin-bottom: 20px; - - font-size: 18px; -} - -.chatpal-search-result-header { - margin: 20px 0; - - font-weight: bold; -} - -.chatpal-search-result-single { - position: relative; - - min-height: 92px; - - margin-bottom: 10px; - - padding: 10px; - - border: 1px solid #e6e6e6; - background-color: white; -} - -.chatpal-search-result-user { - position: relative; - - min-height: 40px; - - margin-bottom: 10px; - - padding: 10px; - - border: 1px solid #e6e6e6; - background-color: white; -} - -.chatpal-search-result-user .chatpal-avatar { - position: absolute; - - width: 36px; - height: 36px; -} - -.chatpal-search-result-user .chatpal-avatar .chatpal-avatar-image { - width: 100%; - height: 100%; - - background-repeat: no-repeat; - background-size: contain; -} - -.chatpal-search-result-user h2 { - margin-bottom: 5px; - padding-left: 45px; -} - -.chatpal-search-result-user .direct-message { - padding-left: 45px; - - font-size: 14px; - font-weight: bold; -} - -.chatpal-channel { - flex: 1 1 auto; -} - -.chatpal-search-result-user .chatpal-channel { - font-weight: bold; -} - -.chatpal-show-more-messages { - margin-bottom: 20px; - - cursor: pointer; - - text-align: center; - - color: #125074; - - font-size: 14px; - font-weight: bold; -} - -.chatpal-show-more-messages:hover { - text-decoration: underline; -} - -.chatpal-search-result-user .direct-message a { - color: #125074; -} - -.chatpal-search-result-user .direct-message a:hover { - text-decoration: underline; -} - -.chatpal-search-result-single h2 { - display: flex; - - margin-bottom: 20px; - align-items: center; -} - -.chatpal-search-result-single .chatpal-avatar { - position: absolute; - - width: 36px; - height: 36px; -} - -.chatpal-search-result-single .chatpal-avatar .chatpal-avatar-image { - width: 100%; - height: 100%; - - background-repeat: no-repeat; - background-size: contain; -} - -.chatpal-search-result-single .chatpal-name { - padding: 0 0 0 46px; - - color: #444444; - - font-size: 14px; - - font-weight: bold; -} - -.chatpal-search-result-single .chatpal-date { - color: #a0a0a0; - - font-size: 12px; - - font-weight: normal; -} - -.chatpal-search-result-single .chatpal-time { - margin-left: 3px; - - color: #a0a0a0; - - font-size: 12px; -} - -.chatpal-search-result-single .chatpal-message { - overflow-x: hidden; - - margin-top: 5px; - padding: 0 0 0 46px; - - line-height: 20px; -} - -.chatpal-search-result-single .chatpal-message em { - background-color: #faf9c8; - - font-style: normal; -} - -.chatpal-search-result-single .chatpal-link { - display: none; - flex: 20; - - color: #125074; -} - -.chatpal-search-result-single .chatpal-link:hover { - text-decoration: underline; -} - -.chatpal-search-result-single:hover .chatpal-link { - display: inline-block; -} - -.chatpal-paging { - margin: 30px 0 50px; - - text-align: center; -} - -.chatpal-paging-text { - position: relative; - top: -2px; - - display: inline-block; -} - -.chatpal-paging .chatpal-paging-button { - display: inline-block; - - cursor: pointer; - - color: #125074; - - font-size: 20px; -} - -.chatpal-paging .chatpal-paging-prev { - padding-right: 10px; -} - -.chatpal-paging .chatpal-paging-next { - padding-left: 10px; -} - -.chatpal-search-info { - margin-top: 40px; - padding: 10px; -} - -.chatpal-search-welcome { - padding-top: 40px; - - text-align: center; -} - -.chatpal-search-result-room h2 { - padding-left: 0 !important; -} - -.chatpal-search-result-list em { - background-color: #faf9c8; - - font-style: normal; -} - -.chatpal-search-result-room .chatpal-link { - display: none; - - margin-left: 3px; - - color: #125074; -} - -.chatpal-search-result-room:hover .chatpal-link { - display: inline-block; -} - -.chatpal-search-result-room .chatpal-link:hover { - text-decoration: underline; -} - -.chatpal-search-pills div { - display: inline-block; - - margin-top: 5px; - - margin-right: 5px; - - padding: 2px 5px; - - border-radius: 3px; - background-color: #f1f1f1; -} - -.apikey h2 { - margin-top: 20px; -} - -.apikey .key { - position: relative; - - margin: 20px 0; - - padding: 10px; - - text-align: center; - - background-color: #f1f1f1; - - font-size: 20px; -} - -.apikey a { - text-decoration: underline; -} - -.chatpal-suggestion { - display: flex; - - padding: 10px; - justify-items: center; -} - -.chatpal-suggestion-text { - flex: 1 1 auto; -} - -.chatpal-suggestion-hint { - display: none; -} - -.rocket-search-suggestion-item.active .chatpal-suggestion-hint { - display: block; -} - -.chatpalProvider .rc-input__icon { - top: 3px; - - left: 10px; - - width: 26px; -} - -.chatpalProvider .rc-input__icon-svg { - font-size: 26px !important; -} - -.chatpalProvider .rocket-search-suggestion-item.active, -.chatpalProvider .rocket-search-suggestion-item:hover { - color: white; - background-color: #125074; -} diff --git a/apps/meteor/app/chatpal-search/client/template/admin.html b/apps/meteor/app/chatpal-search/client/template/admin.html deleted file mode 100644 index 91c31cf49d21..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/admin.html +++ /dev/null @@ -1,56 +0,0 @@ - diff --git a/apps/meteor/app/chatpal-search/client/template/admin.js b/apps/meteor/app/chatpal-search/client/template/admin.js deleted file mode 100644 index dd878d3e91e9..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/admin.js +++ /dev/null @@ -1,91 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { settings } from '../../../settings'; -import { hasPermission } from '../../../authorization'; -import { dispatchToastMessage } from '../../../../client/lib/toast'; -import { validateEmail } from '../../../../lib/emailValidator'; - -Template.ChatpalAdmin.onCreated(function () { - this.validateEmail = validateEmail; - - this.apiKey = new ReactiveVar(); - - const lang = settings.get('Language'); - - this.lang = lang === 'de' || lang === 'en' ? lang : 'en'; - - this.tac = new ReactiveVar(); - - Meteor.call('chatpalUtilsGetTaC', this.lang, (err, data) => { - this.tac.set(data); - }); -}); - -Template.ChatpalAdmin.events({ - 'submit form'(e, t) { - e.preventDefault(); - - const email = e.target.email.value; - const tac = e.target.readtac.checked; - - if (!tac) { - return dispatchToastMessage({ - type: 'error', - message: TAPi18n.__('Chatpal_ERROR_TAC_must_be_checked'), - }); - } - if (!email || email === '') { - return dispatchToastMessage({ - type: 'error', - message: TAPi18n.__('Chatpal_ERROR_Email_must_be_set'), - }); - } - if (!t.validateEmail(email)) { - return dispatchToastMessage({ - type: 'error', - message: TAPi18n.__('Chatpal_ERROR_Email_must_be_valid'), - }); - } - - // TODO register - try { - Meteor.call('chatpalUtilsCreateKey', email, (err, key) => { - if (!key) { - return dispatchToastMessage({ - type: 'error', - message: TAPi18n.__('Chatpal_ERROR_username_already_exists'), - }); - } - - dispatchToastMessage({ - type: 'info', - message: TAPi18n.__('Chatpal_created_key_successfully'), - }); - - t.apiKey.set(key); - }); - } catch (e) { - console.log(e); - dispatchToastMessage({ - type: 'error', - message: TAPi18n.__('Chatpal_ERROR_username_already_exists'), - }); // TODO error messages - } - }, -}); - -// template -Template.ChatpalAdmin.helpers({ - apiKey() { - return Template.instance().apiKey.get(); - }, - isAdmin() { - return hasPermission('manage-chatpal'); - }, - tac() { - return Template.instance().tac.get(); - }, -}); diff --git a/apps/meteor/app/chatpal-search/client/template/result.html b/apps/meteor/app/chatpal-search/client/template/result.html deleted file mode 100644 index 7b05a7b18272..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/result.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - diff --git a/apps/meteor/app/chatpal-search/client/template/result.js b/apps/meteor/app/chatpal-search/client/template/result.js deleted file mode 100644 index 17a91ae4f6b1..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/result.js +++ /dev/null @@ -1,153 +0,0 @@ -import { ReactiveVar } from 'meteor/reactive-var'; -import { Template } from 'meteor/templating'; -import { TAPi18n } from 'meteor/rocketchat:tap-i18n'; - -import { getURL } from '../../../utils'; -import { Subscriptions } from '../../../models/client'; -import { getUserAvatarURL as getAvatarUrl } from '../../../utils/lib/getUserAvatarURL'; -import { formatTime } from '../../../../client/lib/utils/formatTime'; -import { formatDate } from '../../../../client/lib/utils/formatDate'; -import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; - -const getDMUrl = (username) => getURL(`/direct/${username}`); - -Template.ChatpalSearchResultTemplate.onCreated(function () { - this.badRequest = new ReactiveVar(false); - this.resultType = new ReactiveVar(this.data.settings.DefaultResultType); - this.data.parentPayload.resultType = this.resultType.get(); -}); - -Template.ChatpalSearchResultTemplate.events = { - 'click .chatpal-search-typefilter li'(evt, t) { - t.data.parentPayload.resultType = evt.currentTarget.getAttribute('value'); - t.data.payload.start = 0; - t.resultType.set(t.data.parentPayload.resultType); - t.data.search(); - }, - 'click .chatpal-paging-prev'(env, t) { - t.data.payload.start -= t.data.settings.PageSize; - t.data.search(); - }, - 'click .chatpal-paging-next'(env, t) { - t.data.payload.start = (t.data.payload.start || 0) + t.data.settings.PageSize; - t.data.search(); - }, - 'click .chatpal-show-more-messages'(evt, t) { - t.data.parentPayload.resultType = 'Messages'; - t.data.payload.start = 0; - t.data.payload.rows = t.data.settings.PageSize; - t.resultType.set(t.data.parentPayload.resultType); - t.data.search(); - }, -}; - -Template.ChatpalSearchResultTemplate.helpers({ - result() { - return Template.instance().data.result.get(); - }, - searching() { - return Template.instance().data.searching.get(); - }, - resultType() { - return Template.instance().resultType.get(); - }, - navSelected(type) { - return Template.instance().resultType.get() === type ? 'selected' : ''; - }, - resultsFoundForAllSearch() { - const result = Template.instance().data.result.get(); - - if (!result) { - return true; - } - - return result.message.numFound > 0 || result.user.numFound > 0 || result.room.numFound > 0; - }, - moreMessagesThanDisplayed() { - const result = Template.instance().data.result.get(); - - return result.message.docs.length < result.message.numFound; - }, - resultNumFound() { - const result = Template.instance().data.result.get(); - if (result) { - switch (result.message.numFound) { - case 0: - return TAPi18n.__('Chatpal_no_search_results'); - case 1: - return TAPi18n.__('Chatpal_one_search_result'); - default: - return TAPi18n.__('Chatpal_search_results', result.message.numFound); - } - } - }, - resultMessagesOnly() { - return Template.instance().resultType.get() === 'Messages' || Template.instance().resultType.get() === 'Room'; - }, - resultPaging() { - const result = Template.instance().data.result.get(); - const pageSize = Template.instance().data.settings.PageSize; - if (result) { - return { - currentPage: 1 + result.message.start / pageSize, - numOfPages: Math.ceil(result.message.numFound / pageSize), - }; - } - }, -}); - -Template.ChatpalSearchSingleMessage.helpers({ - roomIcon() { - const room = this.r; - if (room && room.t === 'd') { - return 'at'; - } - return roomCoordinator.getIcon(room); - }, - - roomLink() { - return roomCoordinator.getRouteLink(this.r.t, this.r); - }, - - roomName() { - return roomCoordinator.getRoomName(this.r.t, this.r); - }, - - roomNotSubscribed() { - const subscription = Subscriptions.findOne({ rid: this.rid }); - return typeof subscription === 'undefined'; - }, - - time() { - return formatTime(this.created); - }, - date() { - return formatDate(this.created); - }, - getAvatarUrl, -}); - -Template.ChatpalSearchSingleRoom.helpers({ - roomIcon() { - if (this.t === 'd') { - return 'at'; - } - return roomCoordinator.getIcon(this); - }, - roomLink() { - return roomCoordinator.getRouteLink(this.t, this); - }, - roomNotSubscribed() { - const subscription = Subscriptions.findOne({ rid: this.rid }); - return typeof subscription === 'undefined'; - }, -}); - -Template.ChatpalSearchSingleUser.helpers({ - cleanUsername() { - const username = this.user_username || this.username; // varies whether users or messages of users are displayed - return username.replace(/<\/?em>/gi, ''); - }, - getAvatarUrl, - getDMUrl, -}); diff --git a/apps/meteor/app/chatpal-search/client/template/suggestion.html b/apps/meteor/app/chatpal-search/client/template/suggestion.html deleted file mode 100644 index b003193c8af3..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/suggestion.html +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/apps/meteor/app/chatpal-search/client/template/suggestion.js b/apps/meteor/app/chatpal-search/client/template/suggestion.js deleted file mode 100644 index 8f6e64c8775f..000000000000 --- a/apps/meteor/app/chatpal-search/client/template/suggestion.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Template } from 'meteor/templating'; - -Template.ChatpalSuggestionItemTemplate.onCreated(function () { - if (this.data.type === 'link') { - this.data.action = () => { - console.log('an example for an external link'); - }; - } -}); diff --git a/apps/meteor/app/chatpal-search/server/asset/config.js b/apps/meteor/app/chatpal-search/server/asset/config.js deleted file mode 100644 index fa93ed60def7..000000000000 --- a/apps/meteor/app/chatpal-search/server/asset/config.js +++ /dev/null @@ -1,4 +0,0 @@ -import { injectIntoBody } from '../../../ui-master/server'; - -injectIntoBody('chatpal-enter', Assets.getText('server/asset/chatpal-enter.svg')); -injectIntoBody('chatpal-logo-icon-darkblue', Assets.getText('server/asset/chatpal-logo-icon-darkblue.svg')); diff --git a/apps/meteor/app/chatpal-search/server/index.js b/apps/meteor/app/chatpal-search/server/index.js deleted file mode 100644 index 2a7f1094d090..000000000000 --- a/apps/meteor/app/chatpal-search/server/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import './asset/config'; -import './provider/provider'; -import './utils/utils'; diff --git a/apps/meteor/app/chatpal-search/server/provider/index.js b/apps/meteor/app/chatpal-search/server/provider/index.js deleted file mode 100644 index 77eaa2725596..000000000000 --- a/apps/meteor/app/chatpal-search/server/provider/index.js +++ /dev/null @@ -1,444 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { HTTP } from 'meteor/http'; -import { Random } from 'meteor/random'; - -import ChatpalLogger from '../utils/logger'; -import { Rooms, Messages } from '../../../models/server'; - -/** - * Enables HTTP functions on Chatpal Backend - */ -class Backend { - constructor(options) { - this._options = options; - } - - /** - * index a set of Sorl documents - * @param docs - * @returns {boolean} - */ - index(docs) { - const options = { - data: docs, - params: { language: this._options.language }, - ...this._options.httpOptions, - }; - - try { - const response = HTTP.call('POST', `${this._options.baseurl}${this._options.updatepath}`, options); - - if (response.statusCode >= 200 && response.statusCode < 300) { - ChatpalLogger.debug({ msg: `indexed ${docs.length} documents`, data: response.data }); - } else { - throw new Error(response); - } - } catch (e) { - // TODO how to deal with this - ChatpalLogger.error({ msg: 'indexing failed', err: e }); - return false; - } - } - - /** - * remove an entry by type and id - * @param type - * @param id - * @returns {boolean} - */ - remove(type, id) { - ChatpalLogger.debug(`Remove ${type}(${id}) from Index`); - - const options = { - data: { - delete: { - query: `id:${id} AND type:${type}`, - }, - commit: {}, - }, - ...this._options.httpOptions, - }; - - try { - const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options); - - return response.statusCode >= 200 && response.statusCode < 300; - } catch (e) { - return false; - } - } - - count(type) { - return this.query({ type, rows: 0, text: '*' })[type].numFound; - } - - /** - * query with params - * @param params - * @param callback - */ - query(params, callback) { - const options = { - params, - ...this._options.httpOptions, - }; - - ChatpalLogger.debug({ query: options }); - - try { - if (callback) { - HTTP.call('POST', this._options.baseurl + this._options.searchpath, options, (err, result) => { - if (err) { - return callback(err); - } - - callback(undefined, result.data); - }); - } else { - const response = HTTP.call('POST', this._options.baseurl + this._options.searchpath, options); - - if (response.statusCode >= 200 && response.statusCode < 300) { - return response.data; - } - throw new Error(response); - } - } catch (e) { - ChatpalLogger.error({ msg: 'query failed', err: e }); - throw e; - } - } - - suggest(params, callback) { - const options = { - params, - ...this._options.httpOptions, - }; - - HTTP.call('POST', this._options.baseurl + this._options.suggestionpath, options, (err, result) => { - if (err) { - return callback(err); - } - - try { - callback(undefined, result.data.suggestion); - } catch (e) { - callback(e); - } - }); - } - - clear() { - ChatpalLogger.debug('Clear Index'); - - const options = { - data: { - delete: { - query: '*:*', - }, - commit: {}, - }, - ...this._options.httpOptions, - }; - - try { - const response = HTTP.call('POST', this._options.baseurl + this._options.clearpath, options); - - return response.statusCode >= 200 && response.statusCode < 300; - } catch (e) { - return false; - } - } - - /** - * statically ping with configuration - * @param options - * @returns {boolean} - */ - static ping(config) { - const options = { - params: { - stats: true, - }, - ...config.httpOptions, - }; - - try { - const response = HTTP.call('GET', config.baseurl + config.pingpath, options); - - if (response.statusCode >= 200 && response.statusCode < 300) { - return response.data.stats; - } - return false; - } catch (e) { - return false; - } - } -} - -/** - * Enabled batch indexing - */ -class BatchIndexer { - constructor(size, func, ...rest) { - this._size = size; - this._func = func; - this._rest = rest; - this._values = []; - } - - add(value) { - this._values.push(value); - if (this._values.length === this._size) { - this.flush(); - } - } - - flush() { - this._func(this._values, this._rest); // TODO if flush does not work - this._values = []; - } -} - -/** - * Provides index functions to chatpal provider - */ -export default class Index { - /** - * Creates Index Stub - * @param options - * @param clear if a complete reindex should be done - */ - constructor(options, clear, date) { - this._id = Random.id(); - - this._backend = new Backend(options); - - this._options = options; - - this._batchIndexer = new BatchIndexer(this._options.batchSize || 100, (values) => this._backend.index(values)); - - this._bootstrap(clear, date); - } - - /** - * prepare solr documents - * @param type - * @param doc - * @returns {*} - * @private - */ - _getIndexDocument(type, doc) { - switch (type) { - case 'message': - return { - id: doc._id, - rid: doc.rid, - user: doc.u._id, - created: doc.ts, - updated: doc._updatedAt, - text: doc.msg, - type, - }; - case 'room': - return { - id: doc._id, - rid: doc._id, - created: doc.createdAt, - updated: doc.lm ? doc.lm : doc._updatedAt, - type, - room_name: doc.name, - room_announcement: doc.announcement, - room_description: doc.description, - room_topic: doc.topic, - }; - case 'user': - return { - id: doc._id, - created: doc.createdAt, - updated: doc._updatedAt, - type, - user_username: doc.username, - user_name: doc.name, - user_email: doc.emails && doc.emails.map((e) => e.address), - }; - default: - throw new Error(`Cannot index type '${type}'`); - } - } - - /** - * return true if there are messages in the databases which has been created before *date* - * @param date - * @returns {boolean} - * @private - */ - _existsDataOlderThan(date) { - return Messages.model.find({ ts: { $lt: new Date(date) }, t: { $exists: false } }, { limit: 1 }).fetch().length > 0; - } - - _doesRoomCountDiffer() { - return Rooms.find({ t: { $ne: 'd' } }).count() !== this._backend.count('room'); - } - - _doesUserCountDiffer() { - return Meteor.users.find({ active: true }).count() !== this._backend.count('user'); - } - - /** - * Index users by using a database cursor - */ - _indexUsers() { - const cursor = Meteor.users.find({ active: true }); - - ChatpalLogger.debug(`Start indexing ${cursor.count()} users`); - - cursor.forEach((user) => { - this.indexDoc('user', user, false); - }); - - ChatpalLogger.info(`Users indexed successfully (index-id: ${this._id})`); - } - - /** - * Index rooms by database cursor - * @private - */ - _indexRooms() { - const cursor = Rooms.find({ t: { $ne: 'd' } }); - - ChatpalLogger.debug(`Start indexing ${cursor.count()} rooms`); - - cursor.forEach((room) => { - this.indexDoc('room', room, false); - }); - - ChatpalLogger.info(`Rooms indexed successfully (index-id: ${this._id})`); - } - - _indexMessages(date, gap) { - const start = new Date(date - gap); - const end = new Date(date); - - const cursor = Messages.model.find({ ts: { $gt: start, $lt: end }, t: { $exists: false } }); - - ChatpalLogger.debug(`Start indexing ${cursor.count()} messages between ${start.toString()} and ${end.toString()}`); - - cursor.forEach((message) => { - this.indexDoc('message', message, false); - }); - - ChatpalLogger.info(`Messages between ${start.toString()} and ${end.toString()} indexed successfully (index-id: ${this._id})`); - - return start.getTime(); - } - - _run(date, resolve, reject) { - this._running = true; - - if (this._existsDataOlderThan(date) && !this._break) { - Meteor.setTimeout(() => { - date = this._indexMessages(date, (this._options.windowSize || 24) * 3600000); - - this._run(date, resolve, reject); - }, this._options.timeout || 1000); - } else if (this._break) { - ChatpalLogger.info(`stopped bootstrap (index-id: ${this._id})`); - - this._batchIndexer.flush(); - - this._running = false; - - resolve(); - } else { - ChatpalLogger.info(`No messages older than already indexed date ${new Date(date).toString()}`); - - if (this._doesUserCountDiffer() && !this._break) { - this._indexUsers(); - } else { - ChatpalLogger.info('Users already indexed'); - } - - if (this._doesRoomCountDiffer() && !this._break) { - this._indexRooms(); - } else { - ChatpalLogger.info('Rooms already indexed'); - } - - this._batchIndexer.flush(); - - ChatpalLogger.info(`finished bootstrap (index-id: ${this._id})`); - - this._running = false; - - resolve(); - } - } - - _bootstrap(clear, date) { - ChatpalLogger.info('Start bootstrapping'); - - return new Promise((resolve, reject) => { - if (clear) { - this._backend.clear(); - date = new Date().getTime(); - } - - this._run(date, resolve, reject); - }); - } - - static ping(options) { - return Backend.ping(options); - } - - stop() { - this._break = true; - } - - reindex() { - if (!this._running) { - this._bootstrap(true); - } - } - - indexDoc(type, doc, flush = true) { - this._batchIndexer.add(this._getIndexDocument(type, doc)); - - if (flush) { - this._batchIndexer.flush(); - } - - return true; - } - - removeDoc(type, id) { - return this._backend.remove(type, id); - } - - query(text, language, acl, type, start, rows, callback, params = {}) { - this._backend.query( - { - text, - language, - acl, - type, - start, - rows, - ...params, - }, - callback, - ); - } - - suggest(text, language, acl, type, callback) { - this._backend.suggest( - { - text, - language, - acl, - type, - }, - callback, - ); - } -} diff --git a/apps/meteor/app/chatpal-search/server/provider/provider.js b/apps/meteor/app/chatpal-search/server/provider/provider.js deleted file mode 100644 index b8705cf29be3..000000000000 --- a/apps/meteor/app/chatpal-search/server/provider/provider.js +++ /dev/null @@ -1,380 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -import { searchProviderService, SearchProvider } from '../../../search/server'; -import ChatpalLogger from '../utils/logger'; -import { Subscriptions, Rooms } from '../../../models/server'; -import { baseUrl } from '../utils/settings'; -import Index from './index'; - -/** - * The chatpal search provider enables chatpal search. An appropriate backedn has to be specified by settings. - */ -class ChatpalProvider extends SearchProvider { - /** - * Create chatpal provider with some settings for backend and ui - */ - constructor() { - super('chatpalProvider'); - - this.chatpalBaseUrl = `${baseUrl}`; - - ChatpalLogger.debug(`Using ${this.chatpalBaseUrl} as chatpal base url`); - - this._settings.add('Backend', 'select', 'cloud', { - values: [ - { key: 'cloud', i18nLabel: 'Cloud Service' }, - { key: 'onsite', i18nLabel: 'On-Site' }, - ], - i18nLabel: 'Chatpal_Backend', - i18nDescription: 'Chatpal_Backend_Description', - }); - this._settings.add('API_Key', 'string', '', { - enableQuery: [ - { - _id: 'Search.chatpalProvider.Backend', - value: 'cloud', - }, - ], - i18nLabel: 'Chatpal_API_Key', - i18nDescription: 'Chatpal_API_Key_Description', - }); - this._settings.add('Base_URL', 'string', '', { - enableQuery: [ - { - _id: 'Search.chatpalProvider.Backend', - value: 'onsite', - }, - ], - i18nLabel: 'Chatpal_Base_URL', - i18nDescription: 'Chatpal_Base_URL_Description', - }); - this._settings.add('HTTP_Headers', 'string', '', { - enableQuery: [ - { - _id: 'Search.chatpalProvider.Backend', - value: 'onsite', - }, - ], - multiline: true, - i18nLabel: 'Chatpal_HTTP_Headers', - i18nDescription: 'Chatpal_HTTP_Headers_Description', - }); - this._settings.add('Main_Language', 'select', 'en', { - values: [ - { key: 'en', i18nLabel: 'English' }, - { key: 'none', i18nLabel: 'Language_Not_set' }, - { key: 'cs', i18nLabel: 'Czech' }, - { key: 'de', i18nLabel: 'Deutsch' }, - { key: 'el', i18nLabel: 'Greek' }, - { key: 'es', i18nLabel: 'Spanish' }, - { key: 'fi', i18nLabel: 'Finish' }, - { key: 'fr', i18nLabel: 'French' }, - { key: 'hu', i18nLabel: 'Hungarian' }, - { key: 'it', i18nLabel: 'Italian' }, - { key: 'nl', i18nLabel: 'Dutsch' }, - { key: 'pl', i18nLabel: 'Polish' }, - { key: 'pt', i18nLabel: 'Portuguese' }, - { key: 'pt_BR', i18nLabel: 'Brasilian' }, - { key: 'ro', i18nLabel: 'Romanian' }, - { key: 'ru', i18nLabel: 'Russian' }, - { key: 'sv', i18nLabel: 'Swedisch' }, - { key: 'tr', i18nLabel: 'Turkish' }, - { key: 'uk', i18nLabel: 'Ukrainian' }, - ], - i18nLabel: 'Chatpal_Main_Language', - i18nDescription: 'Chatpal_Main_Language_Description', - }); - this._settings.add('DefaultResultType', 'select', 'All', { - values: [ - { key: 'All', i18nLabel: 'Chatpal_All_Results' }, - { key: 'Room', i18nLabel: 'Chatpal_Current_Room_Only' }, - { key: 'Messages', i18nLabel: 'Chatpal_Messages_Only' }, - ], - i18nLabel: 'Chatpal_Default_Result_Type', - i18nDescription: 'Chatpal_Default_Result_Type_Description', - }); - this._settings.add('PageSize', 'int', 15, { - i18nLabel: 'Search_Page_Size', - }); - this._settings.add('SuggestionEnabled', 'boolean', true, { - i18nLabel: 'Chatpal_Suggestion_Enabled', - alert: 'This feature is currently in beta and will be extended in the future', - }); - this._settings.add('IncludeAllPublicChannels', 'boolean', false, { - i18nLabel: 'Chatpal_Include_All_Public_Channels', - i18nDescription: 'Chatpal_Include_All_Public_Channels_Description', - }); - this._settings.add('BatchSize', 'int', 100, { - i18nLabel: 'Chatpal_Batch_Size', - i18nDescription: 'Chatpal_Batch_Size_Description', - }); - this._settings.add('TimeoutSize', 'int', 5000, { - i18nLabel: 'Chatpal_Timeout_Size', - i18nDescription: 'Chatpal_Timeout_Size_Description', - }); - this._settings.add('WindowSize', 'int', 48, { - i18nLabel: 'Chatpal_Window_Size', - i18nDescription: 'Chatpal_Window_Size_Description', - }); - } - - get i18nLabel() { - return 'Chatpal Provider'; - } - - get iconName() { - return 'chatpal-logo-icon-darkblue'; - } - - get resultTemplate() { - return 'ChatpalSearchResultTemplate'; - } - - get suggestionItemTemplate() { - return 'ChatpalSuggestionItemTemplate'; - } - - get supportsSuggestions() { - return this._settings.get('SuggestionEnabled'); - } - - /** - * indexing for messages, rooms and users - * @inheritDoc - */ - on(name, value, payload) { - if (!this.index) { - this.indexFail = true; - return false; - } - - switch (name) { - case 'message.save': - return this.index.indexDoc('message', payload); - case 'user.save': - return this.index.indexDoc('user', payload); - case 'room.save': - return this.index.indexDoc('room', payload); - case 'message.delete': - return this.index.removeDoc('message', value); - case 'user.delete': - return this.index.removeDoc('user', value); - case 'room.delete': - return this.index.removeDoc('room', value); - } - - return true; - } - - /** - * Check if the index has to be deleted and completely new reindexed - * @param reason the reason for the provider start - * @returns {boolean} - * @private - */ - _checkForClear(reason) { - if (reason === 'startup') { - return false; - } - - if (reason === 'switch') { - return true; - } - - return ( - this._indexConfig.backendtype !== this._settings.get('Backend') || - (this._indexConfig.backendtype === 'onsite' && - this._indexConfig.baseurl !== - (this._settings.get('Base_URL').endsWith('/') ? this._settings.get('Base_URL').slice(0, -1) : this._settings.get('Base_URL'))) || - (this._indexConfig.backendtype === 'cloud' && this._indexConfig.httpOptions.headers['X-Api-Key'] !== this._settings.get('API_Key')) || - this._indexConfig.language !== this._settings.get('Main_Language') - ); - } - - /** - * parse string to object that can be used as header for HTTP calls - * @returns {{}} - * @private - */ - _parseHeaders() { - const headers = {}; - const sh = this._settings.get('HTTP_Headers').split('\n'); - sh.forEach(function (d) { - const ds = d.split(':'); - if (ds.length === 2 && ds[0].trim() !== '') { - headers[ds[0]] = ds[1]; - } - }); - return headers; - } - - /** - * ping if configuration has been set correctly - * @param config - * @param resolve if ping was successfull - * @param reject if some error occurs - * @param timeout until ping is repeated - * @private - */ - _ping(config, resolve, reject, timeout = 5000) { - const maxTimeout = 200000; - - const stats = Index.ping(config); - - if (stats) { - ChatpalLogger.debug('ping was successfull'); - resolve({ config, stats }); - } else { - ChatpalLogger.warn(`ping failed, retry in ${timeout} ms`); - - this._pingTimeout = Meteor.setTimeout(() => { - this._ping(config, resolve, reject, Math.min(maxTimeout, 2 * timeout)); - }, timeout); - } - } - - /** - * Get index config based on settings - * @param callback - * @private - */ - _getIndexConfig() { - return new Promise((resolve, reject) => { - const config = { - backendtype: this._settings.get('Backend'), - }; - - if (this._settings.get('Backend') === 'cloud') { - config.baseurl = this.chatpalBaseUrl; - config.language = this._settings.get('Main_Language'); - config.searchpath = 'search/search'; - config.updatepath = 'search/update'; - config.pingpath = 'search/ping'; - config.clearpath = 'search/clear'; - config.suggestionpath = 'search/suggest'; - config.httpOptions = { - headers: { - 'X-Api-Key': this._settings.get('API_Key'), - }, - }; - } else { - config.baseurl = this._settings.get('Base_URL').replace(/\/?$/, '/'); - config.language = this._settings.get('Main_Language'); - config.searchpath = 'chatpal/search'; - config.updatepath = 'chatpal/update'; - config.pingpath = 'chatpal/ping'; - config.clearpath = 'chatpal/clear'; - config.suggestionpath = 'chatpal/suggest'; - config.httpOptions = { - headers: this._parseHeaders(), - }; - } - - config.batchSize = this._settings.get('BatchSize'); - config.timeout = this._settings.get('TimeoutSize'); - config.windowSize = this._settings.get('WindowSize'); - - this._ping(config, resolve, reject); - }); - } - - /** - * @inheritDoc - * @param callback - */ - stop(resolve) { - ChatpalLogger.info('Provider stopped'); - Meteor.clearTimeout(this._pingTimeout); - this.indexFail = false; - this.index && this.index.stop(); - resolve(); - } - - /** - * @inheritDoc - * @param reason - * @param resolve - * @param reject - */ - start(reason, resolve, reject) { - const clear = this._checkForClear(reason); - - ChatpalLogger.debug(`clear = ${clear} with reason '${reason}'`); - - this._getIndexConfig().then((server) => { - this._indexConfig = server.config; - - this._stats = server.stats; - - ChatpalLogger.debug({ config: this._indexConfig }); - ChatpalLogger.debug({ stats: this._stats }); - - this.index = new Index(this._indexConfig, this.indexFail || clear, this._stats.message.oldest || new Date().valueOf()); - - resolve(); - }, reject); - } - - /** - * returns a list of rooms that are allowed to be seen by current user - * @param context - * @private - */ - _getAcl(context) { - let aclRoomsIds = []; - - const subscribedRooms = Subscriptions.find({ 'u._id': context.uid }) - .fetch() - .map((room) => room.rid); - aclRoomsIds = aclRoomsIds.concat(subscribedRooms); - - if (this._settings.get('IncludeAllPublicChannels')) { - const publicRooms = Rooms.findByType('c') - .fetch() - .map((room) => room._id); - aclRoomsIds = aclRoomsIds.concat(publicRooms); - } - - // return unique room ids - return [...new Set(aclRoomsIds)]; - } - - /** - * @inheritDoc - * @returns {*} - */ - search(text, context, payload, callback) { - if (!this.index) { - return callback({ msg: 'Chatpal_currently_not_active' }); - } - - const type = payload.resultType === 'All' ? ['message', 'user', 'room'] : ['message']; - const params = Object.assign({}, payload.custom); - - this.index.query( - text, - this._settings.get('Main_Language'), - payload.resultType === 'Room' ? [context.rid] : this._getAcl(context), - type, - payload.start || 0, - payload.rows || this._settings.get('PageSize'), - callback, - params, - ); - } - - /** - * @inheritDoc - */ - suggest(text, context, payload, callback) { - if (!this.index) { - return callback({ msg: 'Chatpal_currently_not_active' }); - } - - const type = payload.resultType === 'All' ? ['message', 'user', 'room'] : ['message']; - - this.index.suggest(text, this._settings.get('Main_Language'), this._getAcl(context), type, callback); - } -} - -searchProviderService.register(new ChatpalProvider()); diff --git a/apps/meteor/app/chatpal-search/server/utils/logger.js b/apps/meteor/app/chatpal-search/server/utils/logger.js deleted file mode 100644 index bfc73d4ecbc7..000000000000 --- a/apps/meteor/app/chatpal-search/server/utils/logger.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Logger } from '../../../logger'; - -const ChatpalLogger = new Logger('Chatpal Logger'); -export default ChatpalLogger; diff --git a/apps/meteor/app/chatpal-search/server/utils/settings.js b/apps/meteor/app/chatpal-search/server/utils/settings.js deleted file mode 100644 index a1450bc16b6d..000000000000 --- a/apps/meteor/app/chatpal-search/server/utils/settings.js +++ /dev/null @@ -1 +0,0 @@ -export const baseUrl = (process.env.CHATPAL_URL || 'https://api.chatpal.io/v1').replace(/\/?$/, '/'); diff --git a/apps/meteor/app/chatpal-search/server/utils/utils.js b/apps/meteor/app/chatpal-search/server/utils/utils.js deleted file mode 100644 index 2e74b55705db..000000000000 --- a/apps/meteor/app/chatpal-search/server/utils/utils.js +++ /dev/null @@ -1,29 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { HTTP } from 'meteor/http'; - -import { baseUrl } from './settings'; - -Meteor.methods({ - chatpalUtilsCreateKey(email) { - try { - const response = HTTP.call('POST', `${baseUrl}account`, { data: { email, tier: 'free' } }); - if (response.statusCode === 201) { - return response.data.key; - } - return false; - } catch (e) { - return false; - } - }, - chatpalUtilsGetTaC(lang) { - try { - const response = HTTP.call('GET', `${baseUrl}terms/${lang}.html`); - if (response.statusCode === 200) { - return response.content; - } - return undefined; - } catch (e) { - return false; - } - }, -}); diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index 3843b7fcd9e2..a7eaf90c6e97 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -63,7 +63,6 @@ import '../app/meteor-accounts-saml/client'; import '../app/e2e/client'; import '../app/version-check/client'; import '../app/search/client'; -import '../app/chatpal-search/client'; import '../app/lazy-load/client'; import '../app/discussion/client'; import '../app/threads/client'; diff --git a/apps/meteor/client/views/admin/routes.tsx b/apps/meteor/client/views/admin/routes.tsx index 7714a1fa6c37..7a57e95727e5 100644 --- a/apps/meteor/client/views/admin/routes.tsx +++ b/apps/meteor/client/views/admin/routes.tsx @@ -1,9 +1,6 @@ -import React, { lazy } from 'react'; +import { lazy } from 'react'; -import { appLayout } from '../../lib/appLayout'; import { createRouteGroup } from '../../lib/createRouteGroup'; -import BlazeTemplate from '../root/BlazeTemplate'; -import MainLayout from '../root/MainLayout'; export const registerAdminRoute = createRouteGroup( 'admin', @@ -125,17 +122,6 @@ registerAdminRoute('/settings/:group?', { component: lazy(() => import('./settings/SettingsRoute')), }); -registerAdminRoute('/chatpal', { - name: 'chatpal-admin', - action() { - appLayout.render( - - - , - ); - }, -}); - registerAdminRoute('/upgrade/:type?', { name: 'upgrade', component: lazy(() => import('./upgrade/UpgradePage')), diff --git a/apps/meteor/definition/externals/meteor/templating.d.ts b/apps/meteor/definition/externals/meteor/templating.d.ts index 5319c649f839..bd8e956d023b 100644 --- a/apps/meteor/definition/externals/meteor/templating.d.ts +++ b/apps/meteor/definition/externals/meteor/templating.d.ts @@ -24,12 +24,6 @@ declare module 'meteor/blaze' { declare module 'meteor/templating' { interface TemplateStatic { requiresPermission: Blaze.Template>; - ChatpalAdmin: Blaze.Template>; - ChatpalSearchResultTemplate: Blaze.Template>; - ChatpalSearchSingleTemplate: Blaze.Template>; - ChatpalSearchSingleUser: Blaze.Template>; - ChatpalSearchSingleRoom: Blaze.Template>; - ChatpalSuggestionItemTemplate: Blaze.Template>; emojiPicker: Blaze.Template>; lazyloadImage: Blaze.Template>; customFieldsForm: Blaze.Template>; diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index ab6978d3a53b..942a385f8b52 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -890,58 +890,7 @@ "Chatops_Enabled": "Enable Chatops", "Chatops_Title": "Chatops Panel", "Chatops_Username": "Chatops Username", - "Chatpal_AdminPage": "Chatpal Admin Page", - "Chatpal_All_Results": "Everything", - "Chatpal_API_Key": "API Key", - "Chatpal_API_Key_Description": "You don't yet have an API Key? Get one!", - "Chatpal_Backend": "Backend Type", - "Chatpal_Backend_Description": "Select if you want to use Chatpal as a Service or as On-Site Installation", - "Chatpal_Base_URL": "Base Url", - "Chatpal_Base_URL_Description": "Find some description how to run a local instance on github. The URL must be absolue and point to the chatpal core, e.g. http://localhost:8983/solr/chatpal.", - "Chatpal_Batch_Size": "Index Batch Size", - "Chatpal_Batch_Size_Description": "The batch size of index documents (on bootstrapping)", - "Chatpal_channel_not_joined_yet": "Channel not joined yet", - "Chatpal_create_key": "Create Key", - "Chatpal_created_key_successfully": "API-Key created successfully", - "Chatpal_Current_Room_Only": "Same room", - "Chatpal_Default_Result_Type": "Default Result Type", - "Chatpal_Default_Result_Type_Description": "Defines which result type is shown by result. All means that an overview for all types is provided.", "Chat_Duration": "Chat Duration", - "Chatpal_Email_Address": "Email Address", - "Chatpal_ERROR_Email_must_be_set": "Email must be set", - "Chatpal_ERROR_Email_must_be_valid": "Email must be valid", - "Chatpal_ERROR_TAC_must_be_checked": "Terms and Conditions must be checked", - "Chatpal_ERROR_username_already_exists": "Username already exists", - "Chatpal_Get_more_information_about_chatpal_on_our_website": "Get more information about Chatpal on http://chatpal.io!", - "Chatpal_go_to_message": "Jump", - "Chatpal_go_to_room": "Jump", - "Chatpal_go_to_user": "Send direct message", - "Chatpal_HTTP_Headers": "Http Headers", - "Chatpal_HTTP_Headers_Description": "List of HTTP Headers, one header per line. Format: name:value", - "Chatpal_Include_All_Public_Channels": "Include All Public Channels", - "Chatpal_Include_All_Public_Channels_Description": "Search in all public channels, even if you haven't joined them yet.", - "Chatpal_Main_Language": "Main Language", - "Chatpal_Main_Language_Description": "The language that is used most in conversations", - "Chatpal_Messages": "Messages", - "Chatpal_Messages_Only": "Messages", - "Chatpal_More": "More", - "Chatpal_No_Results": "No Results", - "Chatpal_no_search_results": "No result", - "Chatpal_one_search_result": "Found 1 result", - "Chatpal_Rooms": "Rooms", - "Chatpal_run_search": "Search", - "Chatpal_search_page_of": "Page %s of %s", - "Chatpal_search_results": "Found %s results", - "Chatpal_Search_Results": "Search Results", - "Chatpal_Suggestion_Enabled": "Suggestions enabled", - "Chatpal_TAC_read": "I have read the terms and conditions", - "Chatpal_Terms_and_Conditions": "Terms and Conditions", - "Chatpal_Timeout_Size": "Index Timeout", - "Chatpal_Timeout_Size_Description": "The time between 2 index windows in ms (on bootstrapping)", - "Chatpal_Users": "Users", - "Chatpal_Welcome": "Enjoy your search!", - "Chatpal_Window_Size": "Index Window Size", - "Chatpal_Window_Size_Description": "The size of index windows in hours (on bootstrapping)", "Chats_removed": "Chats Removed", "Check_All": "Check All", "Check_if_the_spelling_is_correct": "Check if the spelling is correct", @@ -3122,7 +3071,6 @@ "Manager_added": "Manager added", "Manager_removed": "Manager removed", "Managers": "Managers", - "manage-chatpal": "Manage Chatpal", "Management_Server": "Asterisk Manager Interface (AMI)", "Managing_assets": "Managing assets", "Managing_integrations": "Managing integrations", diff --git a/apps/meteor/private/server/asset/chatpal-enter.svg b/apps/meteor/private/server/asset/chatpal-enter.svg deleted file mode 100644 index fe2bad32e76f..000000000000 --- a/apps/meteor/private/server/asset/chatpal-enter.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/meteor/private/server/asset/chatpal-logo-icon-darkblue.svg b/apps/meteor/private/server/asset/chatpal-logo-icon-darkblue.svg deleted file mode 100644 index 6d30666f538b..000000000000 --- a/apps/meteor/private/server/asset/chatpal-logo-icon-darkblue.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/apps/meteor/server/importPackages.ts b/apps/meteor/server/importPackages.ts index c51a5ade1732..55a1758a392d 100644 --- a/apps/meteor/server/importPackages.ts +++ b/apps/meteor/server/importPackages.ts @@ -88,7 +88,6 @@ import '../app/meteor-accounts-saml/server'; import '../app/e2e/server'; import '../app/version-check/server'; import '../app/search/server'; -import '../app/chatpal-search/server'; import '../app/discussion/server'; import '../app/mail-messages/server'; import '../app/user-status'; diff --git a/apps/meteor/server/startup/migrations/v288.ts b/apps/meteor/server/startup/migrations/v288.ts new file mode 100644 index 000000000000..577d62291323 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v288.ts @@ -0,0 +1,9 @@ +import { addMigration } from '../../lib/migrations'; +import { upsertPermissions } from '../../../app/authorization/server/functions/upsertPermissions'; + +addMigration({ + version: 288, + up() { + upsertPermissions(); + }, +});