summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--client/components/settings/peopleBody.jade4
-rw-r--r--client/components/sidebar/sidebar.jade9
-rw-r--r--models/activities.js9
-rw-r--r--models/users.js169
-rw-r--r--server/notifications/outgoing.js361
-rw-r--r--server/statistics.js138
6 files changed, 360 insertions, 330 deletions
diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade
index ca4bc382..fef1067e 100644
--- a/client/components/settings/peopleBody.jade
+++ b/client/components/settings/peopleBody.jade
@@ -110,7 +110,7 @@ template(name="editUserPopup")
label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
- input.js-profile-fullname(type="text" value=user.profile.fullname autofocus)
+ input.js-profile-fullname(type="text" value=user.profile.fullname)
label
| {{_ 'username'}}
span.error.hide.username-taken
@@ -159,7 +159,7 @@ template(name="newUserPopup")
//label.hide.userId(type="text" value=user._id)
label
| {{_ 'fullname'}}
- input.js-profile-fullname(type="text" value="" autofocus)
+ input.js-profile-fullname(type="text" value="")
label
| {{_ 'username'}}
span.error.hide.username-taken
diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade
index ebcd8486..f0b0e4be 100644
--- a/client/components/sidebar/sidebar.jade
+++ b/client/components/sidebar/sidebar.jade
@@ -245,7 +245,7 @@ template(name="outgoingWebhooksPopup")
b  
.materialCheckBox(class="{{#unless enabled}}is-checked{{/unless}}")
input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
- input.js-outgoing-webhooks-url(type="text" name="url" value=url autofocus)
+ input.js-outgoing-webhooks-url(type="text" name="url" value=url)
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" value=token name="token")
select.js-outgoing-webhooks-type(name="type")
each _type in types
@@ -257,7 +257,7 @@ template(name="outgoingWebhooksPopup")
input(type="hidden" value=_id name="id")
input.primary.wide(type="submit" value="{{_ 'save'}}")
form.integration-form
- input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" autofocus)
+ input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title")
input.js-outgoing-webhooks-url(placeholder="{{_ 'URL' }}" type="text" name="url")
input.js-outgoing-webhooks-token(placeholder="{{_ 'webhook-token' }}" type="text" name="token")
select.js-outgoing-webhooks-type(name="type")
@@ -267,7 +267,10 @@ template(name="outgoingWebhooksPopup")
template(name="boardMenuPopup")
ul.pop-over-list
- li: a.js-custom-fields {{_ 'custom-fields'}}
+ li
+ a.js-custom-fields
+ i.fa.fa-list-alt
+ | {{_ 'custom-fields'}}
li
a.js-open-archives
i.fa.fa-archive
diff --git a/models/activities.js b/models/activities.js
index 19e3fb7d..568859a9 100644
--- a/models/activities.js
+++ b/models/activities.js
@@ -108,7 +108,7 @@ if (Meteor.isServer) {
let participants = [];
let watchers = [];
let title = 'act-activity-notify';
- let board = null;
+ const board = Boards.findOne(activity.boardId);
const description = `act-${activity.activityType}`;
const params = {
activityId: activity._id,
@@ -122,8 +122,11 @@ if (Meteor.isServer) {
params.userId = activity.userId;
}
if (activity.boardId) {
- board = activity.board();
- params.board = board.title;
+ if (board.title.length > 0) {
+ params.board = board.title;
+ } else {
+ params.board = '';
+ }
title = 'act-withBoardTitle';
params.url = board.absoluteUrl();
params.boardId = activity.boardId;
diff --git a/models/users.js b/models/users.js
index 00076253..d56f14ff 100644
--- a/models/users.js
+++ b/models/users.js
@@ -620,44 +620,6 @@ Users.mutations({
});
Meteor.methods({
- setCreateUser(fullname, username, password, isAdmin, isActive, email) {
- if (Meteor.user().isAdmin) {
- check(fullname, String);
- check(username, String);
- check(password, String);
- check(isAdmin, String);
- check(isActive, String);
- check(email, String);
-
- const nUsersWithUsername = Users.find({ username }).count();
- const nUsersWithEmail = Users.find({ email }).count();
- if (nUsersWithUsername > 0) {
- throw new Meteor.Error('username-already-taken');
- } else if (nUsersWithEmail > 0) {
- throw new Meteor.Error('email-already-taken');
- } else {
- Accounts.createUser({
- fullname,
- username,
- password,
- isAdmin,
- isActive,
- email: email.toLowerCase(),
- from: 'admin',
- });
- }
- }
- },
- setUsername(username, userId) {
- check(username, String);
- check(userId, String);
- const nUsersWithUsername = Users.find({ username }).count();
- if (nUsersWithUsername > 0) {
- throw new Meteor.Error('username-already-taken');
- } else {
- Users.update(userId, { $set: { username } });
- }
- },
setListSortBy(value) {
check(value, String);
Meteor.user().setListSortBy(value);
@@ -678,51 +640,97 @@ Meteor.methods({
check(limit, Number);
Meteor.user().setShowCardsCountAt(limit);
},
- setEmail(email, userId) {
- if (Array.isArray(email)) {
- email = email.shift();
- }
- check(email, String);
- const existingUser = Users.findOne(
- { 'emails.address': email },
- { fields: { _id: 1 } },
- );
- if (existingUser) {
- throw new Meteor.Error('email-already-taken');
- } else {
- Users.update(userId, {
- $set: {
- emails: [
- {
- address: email,
- verified: false,
- },
- ],
- },
- });
- }
- },
- setUsernameAndEmail(username, email, userId) {
- check(username, String);
- if (Array.isArray(email)) {
- email = email.shift();
- }
- check(email, String);
- check(userId, String);
- Meteor.call('setUsername', username, userId);
- Meteor.call('setEmail', email, userId);
- },
- setPassword(newPassword, userId) {
- check(userId, String);
- check(newPassword, String);
- if (Meteor.user().isAdmin) {
- Accounts.setPassword(userId, newPassword);
- }
- },
});
if (Meteor.isServer) {
Meteor.methods({
+ setCreateUser(fullname, username, password, isAdmin, isActive, email) {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ check(fullname, String);
+ check(username, String);
+ check(password, String);
+ check(isAdmin, String);
+ check(isActive, String);
+ check(email, String);
+
+ const nUsersWithUsername = Users.find({ username }).count();
+ const nUsersWithEmail = Users.find({ email }).count();
+ if (nUsersWithUsername > 0) {
+ throw new Meteor.Error('username-already-taken');
+ } else if (nUsersWithEmail > 0) {
+ throw new Meteor.Error('email-already-taken');
+ } else {
+ Accounts.createUser({
+ fullname,
+ username,
+ password,
+ isAdmin,
+ isActive,
+ email: email.toLowerCase(),
+ from: 'admin',
+ });
+ }
+ }
+ },
+ setUsername(username, userId) {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ check(username, String);
+ check(userId, String);
+ const nUsersWithUsername = Users.find({ username }).count();
+ if (nUsersWithUsername > 0) {
+ throw new Meteor.Error('username-already-taken');
+ } else {
+ Users.update(userId, { $set: { username } });
+ }
+ }
+ },
+ setEmail(email, userId) {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ if (Array.isArray(email)) {
+ email = email.shift();
+ }
+ check(email, String);
+ const existingUser = Users.findOne(
+ { 'emails.address': email },
+ { fields: { _id: 1 } },
+ );
+ if (existingUser) {
+ throw new Meteor.Error('email-already-taken');
+ } else {
+ Users.update(userId, {
+ $set: {
+ emails: [
+ {
+ address: email,
+ verified: false,
+ },
+ ],
+ },
+ });
+ }
+ }
+ },
+ setUsernameAndEmail(username, email, userId) {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ check(username, String);
+ if (Array.isArray(email)) {
+ email = email.shift();
+ }
+ check(email, String);
+ check(userId, String);
+ Meteor.call('setUsername', username, userId);
+ Meteor.call('setEmail', email, userId);
+ }
+ },
+ setPassword(newPassword, userId) {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ check(userId, String);
+ check(newPassword, String);
+ if (Meteor.user().isAdmin) {
+ Accounts.setPassword(userId, newPassword);
+ }
+ }
+ },
// we accept userId, username, email
inviteUserToBoard(username, boardId) {
check(username, String);
@@ -754,8 +762,9 @@ if (Meteor.isServer) {
throw new Meteor.Error('error-user-notAllowSelf');
} else {
if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist');
- if (Settings.findOne().disableRegistration)
+ if (Settings.findOne({ disableRegistration: true })) {
throw new Meteor.Error('error-user-notCreated');
+ }
// Set in lowercase email before creating account
const email = username.toLowerCase();
username = email.substring(0, posAt);
diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js
index 5bc2c540..9a741ea1 100644
--- a/server/notifications/outgoing.js
+++ b/server/notifications/outgoing.js
@@ -1,192 +1,199 @@
-const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
- HTTP.post(url, options, (err, res) => {
- if (err) {
- resolve(null, err.response);
- } else {
- resolve(null, res);
- }
+if (Meteor.isServer) {
+ const postCatchError = Meteor.wrapAsync((url, options, resolve) => {
+ HTTP.post(url, options, (err, res) => {
+ if (err) {
+ resolve(null, err.response);
+ } else {
+ resolve(null, res);
+ }
+ });
});
-});
-const Lock = {
- _lock: {},
- _timer: {},
- echoDelay: 500, // echo should be happening much faster
- normalDelay: 1e3, // normally user typed comment will be much slower
- ECHO: 2,
- NORMAL: 1,
- NULL: 0,
- has(id, value) {
- const existing = this._lock[id];
- let ret = this.NULL;
- if (existing) {
- ret = existing === value ? this.ECHO : this.NORMAL;
- }
- return ret;
- },
- clear(id, delay) {
- const previous = this._timer[id];
- if (previous) {
- Meteor.clearTimeout(previous);
- }
- this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);
- },
- set(id, value) {
- const state = this.has(id, value);
- let delay = this.normalDelay;
- if (state === this.ECHO) {
- delay = this.echoDelay;
- }
- if (!value) {
- // user commented, we set a lock
- value = 1;
- }
- this._lock[id] = value;
- this.clear(id, delay); // always auto reset the locker after delay
- },
- unset(id) {
- delete this._lock[id];
- },
-};
+ const Lock = {
+ _lock: {},
+ _timer: {},
+ echoDelay: 500, // echo should be happening much faster
+ normalDelay: 1e3, // normally user typed comment will be much slower
+ ECHO: 2,
+ NORMAL: 1,
+ NULL: 0,
+ has(id, value) {
+ const existing = this._lock[id];
+ let ret = this.NULL;
+ if (existing) {
+ ret = existing === value ? this.ECHO : this.NORMAL;
+ }
+ return ret;
+ },
+ clear(id, delay) {
+ const previous = this._timer[id];
+ if (previous) {
+ Meteor.clearTimeout(previous);
+ }
+ this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay);
+ },
+ set(id, value) {
+ const state = this.has(id, value);
+ let delay = this.normalDelay;
+ if (state === this.ECHO) {
+ delay = this.echoDelay;
+ }
+ if (!value) {
+ // user commented, we set a lock
+ value = 1;
+ }
+ this._lock[id] = value;
+ this.clear(id, delay); // always auto reset the locker after delay
+ },
+ unset(id) {
+ delete this._lock[id];
+ },
+ };
-const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
- process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
- 'cardId',
- 'listId',
- 'oldListId',
- 'boardId',
- 'comment',
- 'user',
- 'card',
- 'commentId',
- 'swimlaneId',
-];
-const responseFunc = data => {
- const paramCommentId = data.commentId;
- const paramCardId = data.cardId;
- const paramBoardId = data.boardId;
- const newComment = data.comment;
- if (paramCardId && paramBoardId && newComment) {
- // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
- const comment = CardComments.findOne({
- _id: paramCommentId,
- cardId: paramCardId,
- boardId: paramBoardId,
- });
- const board = Boards.findOne(paramBoardId);
- const card = Cards.findOne(paramCardId);
- if (board && card) {
- if (comment) {
- Lock.set(comment._id, newComment);
- CardComments.direct.update(comment._id, {
- $set: {
+ const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES &&
+ process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [
+ 'cardId',
+ 'listId',
+ 'oldListId',
+ 'boardId',
+ 'comment',
+ 'user',
+ 'card',
+ 'commentId',
+ 'swimlaneId',
+ ];
+ const responseFunc = data => {
+ const paramCommentId = data.commentId;
+ const paramCardId = data.cardId;
+ const paramBoardId = data.boardId;
+ const newComment = data.comment;
+ if (paramCardId && paramBoardId && newComment) {
+ // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
+ const comment = CardComments.findOne({
+ _id: paramCommentId,
+ cardId: paramCardId,
+ boardId: paramBoardId,
+ });
+ const board = Boards.findOne(paramBoardId);
+ const card = Cards.findOne(paramCardId);
+ if (board && card) {
+ if (comment) {
+ Lock.set(comment._id, newComment);
+ CardComments.direct.update(comment._id, {
+ $set: {
+ text: newComment,
+ },
+ });
+ }
+ } else {
+ const userId = data.userId;
+ if (userId) {
+ const inserted = CardComments.direct.insert({
text: newComment,
- },
- });
- }
- } else {
- const userId = data.userId;
- if (userId) {
- const inserted = CardComments.direct.insert({
- text: newComment,
- userId,
- cardId,
- boardId,
- });
- Lock.set(inserted._id, newComment);
+ userId,
+ cardId,
+ boardId,
+ });
+ Lock.set(inserted._id, newComment);
+ }
}
}
- }
-};
-Meteor.methods({
- outgoingWebhooks(integration, description, params) {
- check(integration, Object);
- check(description, String);
- check(params, Object);
- this.unblock();
+ };
+ Meteor.methods({
+ outgoingWebhooks(integration, description, params) {
+ if (Meteor.user()) {
+ check(integration, Object);
+ check(description, String);
+ check(params, Object);
+ this.unblock();
- // label activity did not work yet, see wekan/models/activities.js
- const quoteParams = _.clone(params);
- const clonedParams = _.clone(params);
- [
- 'card',
- 'list',
- 'oldList',
- 'board',
- 'oldBoard',
- 'comment',
- 'checklist',
- 'swimlane',
- 'oldSwimlane',
- 'label',
- 'attachment',
- ].forEach(key => {
- if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
- });
+ // label activity did not work yet, see wekan/models/activities.js
+ const quoteParams = _.clone(params);
+ const clonedParams = _.clone(params);
+ [
+ 'card',
+ 'list',
+ 'oldList',
+ 'board',
+ 'oldBoard',
+ 'comment',
+ 'checklist',
+ 'swimlane',
+ 'oldSwimlane',
+ 'label',
+ 'attachment',
+ ].forEach(key => {
+ if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`;
+ });
- const userId = params.userId ? params.userId : integrations[0].userId;
- const user = Users.findOne(userId);
- const text = `${params.user} ${TAPi18n.__(
- description,
- quoteParams,
- user.getLanguage(),
- )}\n${params.url}`;
+ const userId = params.userId ? params.userId : integrations[0].userId;
+ const user = Users.findOne(userId);
+ const text = `${params.user} ${TAPi18n.__(
+ description,
+ quoteParams,
+ user.getLanguage(),
+ )}\n${params.url}`;
- if (text.length === 0) return;
+ if (text.length === 0) return;
- const value = {
- text: `${text}`,
- };
+ const value = {
+ text: `${text}`,
+ };
- webhooksAtbts.forEach(key => {
- if (params[key]) value[key] = params[key];
- });
- value.description = description;
- //integrations.forEach(integration => {
- const is2way = integration.type === Integrations.Const.TWOWAY;
- const token = integration.token || '';
- const headers = {
- 'Content-Type': 'application/json',
- };
- if (token) headers['X-Wekan-Token'] = token;
- const options = {
- headers,
- data: is2way ? { description, ...clonedParams } : value,
- };
- const url = integration.url;
- if (is2way) {
- const cid = params.commentId;
- const comment = params.comment;
- const lockState = cid && Lock.has(cid, comment);
- if (cid && lockState !== Lock.NULL) {
- // it's a comment and there is a previous lock
- return;
- } else if (cid) {
- Lock.set(cid, comment); // set a lock here
- }
- }
- const response = postCatchError(url, options);
+ webhooksAtbts.forEach(key => {
+ if (params[key]) value[key] = params[key];
+ });
+ value.description = description;
+ //integrations.forEach(integration => {
+ const is2way = integration.type === Integrations.Const.TWOWAY;
+ const token = integration.token || '';
+ const headers = {
+ 'Content-Type': 'application/json',
+ };
+ if (token) headers['X-Wekan-Token'] = token;
+ const options = {
+ headers,
+ data: is2way ? { description, ...clonedParams } : value,
+ };
- if (
- response &&
- response.statusCode &&
- response.statusCode >= 200 &&
- response.statusCode < 300
- ) {
- if (is2way) {
- const data = response.data; // only an JSON encoded response will be actioned
- if (data) {
- try {
- responseFunc(data);
- } catch (e) {
- throw new Meteor.Error('error-process-data');
+ if (!Integrations.findOne({ url: integration.url })) return;
+
+ const url = integration.url;
+
+ if (is2way) {
+ const cid = params.commentId;
+ const comment = params.comment;
+ const lockState = cid && Lock.has(cid, comment);
+ if (cid && lockState !== Lock.NULL) {
+ // it's a comment and there is a previous lock
+ return;
+ } else if (cid) {
+ Lock.set(cid, comment); // set a lock here
}
}
+ const response = postCatchError(url, options);
+
+ if (
+ response &&
+ response.statusCode &&
+ response.statusCode >= 200 &&
+ response.statusCode < 300
+ ) {
+ if (is2way) {
+ const data = response.data; // only an JSON encoded response will be actioned
+ if (data) {
+ try {
+ responseFunc(data);
+ } catch (e) {
+ throw new Meteor.Error('error-process-data');
+ }
+ }
+ }
+ return response; // eslint-disable-line consistent-return
+ } else {
+ throw new Meteor.Error('error-invalid-webhook-response');
+ }
}
- return response; // eslint-disable-line consistent-return
- } else {
- throw new Meteor.Error('error-invalid-webhook-response');
- }
- //});
- },
-});
+ },
+ });
+}
diff --git a/server/statistics.js b/server/statistics.js
index 997fd86f..0ead840f 100644
--- a/server/statistics.js
+++ b/server/statistics.js
@@ -1,68 +1,76 @@
import { MongoInternals } from 'meteor/mongo';
-Meteor.methods({
- getStatistics() {
- const os = require('os');
- const pjson = require('/package.json');
- const statistics = {};
- let wekanVersion = pjson.version;
- wekanVersion = wekanVersion.replace('v', '');
- statistics.version = wekanVersion;
- statistics.os = {
- type: os.type(),
- platform: os.platform(),
- arch: os.arch(),
- release: os.release(),
- uptime: os.uptime(),
- loadavg: os.loadavg(),
- totalmem: os.totalmem(),
- freemem: os.freemem(),
- cpus: os.cpus(),
- };
- let nodeVersion = process.version;
- nodeVersion = nodeVersion.replace('v', '');
- statistics.process = {
- nodeVersion,
- pid: process.pid,
- uptime: process.uptime(),
- };
- // Remove beginning of Meteor release text METEOR@
- let meteorVersion = Meteor.release;
- meteorVersion = meteorVersion.replace('METEOR@', '');
- statistics.meteor = {
- meteorVersion,
- };
- // Thanks to RocketChat for MongoDB version detection !
- // https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
- let mongoVersion;
- let mongoStorageEngine;
- let mongoOplogEnabled;
- try {
- const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
- oplogEnabled = Boolean(
- mongo._oplogHandle && mongo._oplogHandle.onOplogEntry,
- );
- const { version, storageEngine } = Promise.await(
- mongo.db.command({ serverStatus: 1 }),
- );
- mongoVersion = version;
- mongoStorageEngine = storageEngine.name;
- mongoOplogEnabled = oplogEnabled;
- } catch (e) {
- try {
- const { version } = Promise.await(mongo.db.command({ buildinfo: 1 }));
- mongoVersion = version;
- mongoStorageEngine = 'unknown';
- } catch (e) {
- mongoVersion = 'unknown';
- mongoStorageEngine = 'unknown';
+if (Meteor.isServer) {
+ Meteor.methods({
+ getStatistics() {
+ if (Meteor.user() && Meteor.user().isAdmin) {
+ const os = require('os');
+ const pjson = require('/package.json');
+ const statistics = {};
+ let wekanVersion = pjson.version;
+ wekanVersion = wekanVersion.replace('v', '');
+ statistics.version = wekanVersion;
+ statistics.os = {
+ type: os.type(),
+ platform: os.platform(),
+ arch: os.arch(),
+ release: os.release(),
+ uptime: os.uptime(),
+ loadavg: os.loadavg(),
+ totalmem: os.totalmem(),
+ freemem: os.freemem(),
+ cpus: os.cpus(),
+ };
+ let nodeVersion = process.version;
+ nodeVersion = nodeVersion.replace('v', '');
+ statistics.process = {
+ nodeVersion,
+ pid: process.pid,
+ uptime: process.uptime(),
+ };
+ // Remove beginning of Meteor release text METEOR@
+ let meteorVersion = Meteor.release;
+ meteorVersion = meteorVersion.replace('METEOR@', '');
+ statistics.meteor = {
+ meteorVersion,
+ };
+ // Thanks to RocketChat for MongoDB version detection !
+ // https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
+ let mongoVersion;
+ let mongoStorageEngine;
+ let mongoOplogEnabled;
+ try {
+ const { mongo } = MongoInternals.defaultRemoteCollectionDriver();
+ oplogEnabled = Boolean(
+ mongo._oplogHandle && mongo._oplogHandle.onOplogEntry,
+ );
+ const { version, storageEngine } = Promise.await(
+ mongo.db.command({ serverStatus: 1 }),
+ );
+ mongoVersion = version;
+ mongoStorageEngine = storageEngine.name;
+ mongoOplogEnabled = oplogEnabled;
+ } catch (e) {
+ try {
+ const { version } = Promise.await(
+ mongo.db.command({ buildinfo: 1 }),
+ );
+ mongoVersion = version;
+ mongoStorageEngine = 'unknown';
+ } catch (e) {
+ mongoVersion = 'unknown';
+ mongoStorageEngine = 'unknown';
+ }
+ }
+ statistics.mongo = {
+ mongoVersion,
+ mongoStorageEngine,
+ mongoOplogEnabled,
+ };
+ return statistics;
+ } else {
+ return false;
}
- }
- statistics.mongo = {
- mongoVersion,
- mongoStorageEngine,
- mongoOplogEnabled,
- };
- return statistics;
- },
-});
+ },
+ });
+}