summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2017-03-05 19:59:19 +0200
committerLauri Ojansivu <x@xet7.org>2017-03-05 19:59:19 +0200
commitcbc3c8224fbed643780c5be20221269b62e50ac6 (patch)
tree5715424ab1454463a3f8ca458729347bbc014f5b
parent7b68f1901e8bd81ec8fbebb60a3f4e057b57d06d (diff)
parent7c9a30d8fe3ffdf4b56cdc816bfc51f60881d55f (diff)
downloadwekan-cbc3c8224fbed643780c5be20221269b62e50ac6.tar.gz
wekan-cbc3c8224fbed643780c5be20221269b62e50ac6.tar.bz2
wekan-cbc3c8224fbed643780c5be20221269b62e50ac6.zip
Merge branch 'lkisme-devel' into devel
Add Admin Panel: 1) Disable Self-Registration and invite users 2) SMTP settings. Adding Admin user in mongo cli: 1) Use database that has wekan data, for example: use admin; 2) Add Admin rights to some Wekan username: db.users.update({username:'admin-username-here'},{$set:{isAdmin:true}}) Hiding Admin panel by removing Admin rights: use admin; db.settings.remove({});
-rw-r--r--.eslintrc.json4
-rw-r--r--client/components/main/layouts.js2
-rw-r--r--client/components/settings/invitationCode.jade5
-rw-r--r--client/components/settings/invitationCode.js6
-rw-r--r--client/components/settings/settingBody.jade71
-rw-r--r--client/components/settings/settingBody.js128
-rw-r--r--client/components/settings/settingBody.styl112
-rw-r--r--client/components/settings/settingHeader.jade21
-rw-r--r--client/components/settings/settingHeader.styl25
-rw-r--r--client/components/users/userHeader.jade2
-rw-r--r--client/components/users/userHeader.js3
-rw-r--r--config/accounts.js15
-rw-r--r--config/router.js20
-rw-r--r--i18n/en.i18n.json25
-rw-r--r--models/invitationCodes.js45
-rw-r--r--models/settings.js116
-rw-r--r--models/users.js45
-rw-r--r--server/publications/settings.js13
-rw-r--r--server/publications/users.js8
19 files changed, 658 insertions, 8 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index 4808d873..0caa7a01 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -119,6 +119,8 @@
"allowIsBoardMember": true,
"allowIsBoardMemberByCard": true,
"Emoji": true,
- "Checklists": true
+ "Checklists": true,
+ "Settings": true,
+ "InvitationCodes": true
}
}
diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js
index 3df17f41..1e50b01a 100644
--- a/client/components/main/layouts.js
+++ b/client/components/main/layouts.js
@@ -1,4 +1,6 @@
Meteor.subscribe('boards');
+Meteor.subscribe('setting');
+Meteor.subscribe('user-admin');
BlazeLayout.setRoot('body');
diff --git a/client/components/settings/invitationCode.jade b/client/components/settings/invitationCode.jade
new file mode 100644
index 00000000..171a2663
--- /dev/null
+++ b/client/components/settings/invitationCode.jade
@@ -0,0 +1,5 @@
+template(name='invitationCode')
+ .at-input#invitationcode
+ label(for='at-field-code') {{_ 'invitation-code'}}
+
+ input#at-field-invitationcode(type="text" name='at-field-invitationcode' placeholder="{{_ 'invitation-code'}}")
diff --git a/client/components/settings/invitationCode.js b/client/components/settings/invitationCode.js
new file mode 100644
index 00000000..e712c89a
--- /dev/null
+++ b/client/components/settings/invitationCode.js
@@ -0,0 +1,6 @@
+Template.invitationCode.onRendered(() => {
+ const disableRegistration = Settings.findOne().disableRegistration;
+ if(!disableRegistration){
+ $('#invitationcode').hide();
+ }
+});
diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade
new file mode 100644
index 00000000..fdab3173
--- /dev/null
+++ b/client/components/settings/settingBody.jade
@@ -0,0 +1,71 @@
+template(name="setting")
+ .setting-content
+ .content-title
+ span {{_ 'settings'}}
+ .content-body
+ .side-menu
+ ul
+ li.active
+ a.js-setting-menu(data-id="registration-setting") {{_ 'registration'}}
+ li
+ a.js-setting-menu(data-id="email-setting") {{_ 'email'}}
+ .main-body
+ if loading.get
+ +spinner
+ else if generalSetting.get
+ +general
+ else if emailSetting.get
+ +email
+
+template(name="general")
+ ul#registration-setting.setting-detail
+ li
+ a.flex.js-toggle-registration
+ .materialCheckBox(class="{{#if currentSetting.disableRegistration}}is-checked{{/if}}")
+
+ span {{_ 'disable-self-registration'}}
+ li
+ .invite-people(class="{{#if currentSetting.disableRegistration}}{{else}}hide{{/if}}")
+ ul
+ li
+ .title {{_ 'invite-people'}}
+ textarea#email-to-invite.form-control(rows='5', placeholder="{{_ 'email-addresses'}}")
+ li
+ .title {{_ 'to-boards'}}
+ .bg-white
+ each boards
+ a.option.flex.js-toggle-board-choose(id= _id)
+ .materialCheckBox(data-id= _id)
+
+ span= title
+
+ li
+ button.js-email-invite.primary {{_ 'invite'}}
+
+template(name='email')
+ ul#email-setting.setting-detail
+ li.smtp-form
+ .title {{_ 'smtp-host'}}
+ .description {{_ 'smtp-host-description'}}
+ .form-group
+ input.form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}")
+ li.smtp-form
+ .title {{_ 'smtp-port'}}
+ .description {{_ 'smtp-port-description'}}
+ .form-group
+ input.form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}")
+ li.smtp-form
+ .title {{_ 'smtp-username'}}
+ .form-group
+ input.form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
+ li.smtp-form
+ .title {{_ 'smtp-password'}}
+ .form-group
+ input.form-control#mail-server-password(type="text", placeholder="{{_ 'password'}}" value="{{currentSetting.mailServer.password}}")
+ li.smtp-form
+ .title {{_ 'send-from'}}
+ .form-group
+ input.form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}")
+
+ li
+ button.js-save.primary Save
diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js
new file mode 100644
index 00000000..5ae982f7
--- /dev/null
+++ b/client/components/settings/settingBody.js
@@ -0,0 +1,128 @@
+Meteor.subscribe('setting');
+Meteor.subscribe('mailServer');
+
+BlazeComponent.extendComponent({
+ onCreated() {
+ this.error = new ReactiveVar('');
+ this.loading = new ReactiveVar(false);
+ this.generalSetting = new ReactiveVar(true);
+ this.emailSetting = new ReactiveVar(false);
+ },
+
+ setError(error) {
+ this.error.set(error);
+ },
+
+ setLoading(w) {
+ this.loading.set(w);
+ },
+
+ checkField(selector) {
+ const value = $(selector).val();
+ if(!value || value.trim() === ''){
+ $(selector).parents('li.smtp-form').addClass('has-error');
+ throw Error('blank field');
+ } else {
+ return value;
+ }
+ },
+
+ currentSetting(){
+ return Settings.findOne();
+ },
+
+ boards() {
+ return Boards.find({
+ archived: false,
+ 'members.userId': Meteor.userId(),
+ 'members.isAdmin': true,
+ }, {
+ sort: ['title'],
+ });
+ },
+ toggleRegistration(){
+ this.setLoading(true);
+ const registrationClosed = this.currentSetting().disableRegistration;
+ Settings.update(Settings.findOne()._id, {$set:{disableRegistration: !registrationClosed}});
+ this.setLoading(false);
+ if(registrationClosed){
+ $('.invite-people').slideUp();
+ }else{
+ $('.invite-people').slideDown();
+ }
+ },
+
+ switchMenu(event){
+ const target = $(event.target);
+ if(!target.hasClass('active')){
+ $('.side-menu li.active').removeClass('active');
+ target.parent().addClass('active');
+ const targetID = target.data('id');
+ this.generalSetting.set('registration-setting' === targetID);
+ this.emailSetting.set('email-setting' === targetID);
+ }
+ },
+
+ checkBoard(event){
+ let target = $(event.target);
+ if(!target.hasClass('js-toggle-board-choose')){
+ target = target.parent();
+ }
+ const checkboxId = target.attr('id');
+ $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked');
+ $(`#${checkboxId}`).toggleClass('is-checked');
+ },
+
+ inviteThroughEmail(){
+ const emails = $('#email-to-invite').val().trim().split('\n').join(',').split(',');
+ const boardsToInvite = [];
+ $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () {
+ boardsToInvite.push($(this).data('id'));
+ });
+ const validEmails = [];
+ emails.forEach((email) => {
+ if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
+ validEmails.push(email.trim());
+ }
+ });
+ if (validEmails.length) {
+ this.setLoading(true);
+ Meteor.call('sendInvitation', validEmails, boardsToInvite, () => {
+ // if (!err) {
+ // TODO - show more info to user
+ // }
+ this.setLoading(false);
+ });
+ }
+ },
+
+ saveMailServerInfo(){
+ this.setLoading(true);
+ $('li').removeClass('has-error');
+
+ try{
+ const host = this.checkField('#mail-server-host');
+ const port = this.checkField('#mail-server-port');
+ const username = this.checkField('#mail-server-username');
+ const password = this.checkField('#mail-server-password');
+ const from = this.checkField('#mail-server-from');
+ Settings.update(Settings.findOne()._id, {$set:{'mailServer.host':host, 'mailServer.port': port, 'mailServer.username': username,
+ 'mailServer.password': password, 'mailServer.from': from}});
+ } catch (e) {
+ return;
+ } finally {
+ this.setLoading(false);
+ }
+
+ },
+
+ events(){
+ return [{
+ 'click a.js-toggle-registration': this.toggleRegistration,
+ 'click a.js-setting-menu': this.switchMenu,
+ 'click a.js-toggle-board-choose': this.checkBoard,
+ 'click button.js-email-invite': this.inviteThroughEmail,
+ 'click button.js-save': this.saveMailServerInfo,
+ }];
+ },
+}).register('setting');
diff --git a/client/components/settings/settingBody.styl b/client/components/settings/settingBody.styl
new file mode 100644
index 00000000..118d364c
--- /dev/null
+++ b/client/components/settings/settingBody.styl
@@ -0,0 +1,112 @@
+.flex
+ display: -webkit-box
+ display: -moz-box
+ display: -webkit-flex
+ display: -moz-flex
+ display: -ms-flexbox
+ display: flex
+
+.setting-content
+ padding 30px
+ color: #727479
+ background: #dedede
+ width 100%
+ height 100%
+ position: absolute;
+
+ .content-title
+ font-size 20px
+
+ .content-body
+ display flex
+ padding-top 15px
+ height 100%
+
+ .side-menu
+ background-color: #f7f7f7;
+ border: 1px solid #f0f0f0;
+ border-radius: 4px;
+ width: 250px;
+ box-shadow: inset -1px -1px 3px rgba(0,0,0,.05);
+
+ ul
+
+ li
+ margin: 0.1rem 0.2rem;
+
+ &.active
+ background #fff
+ box-shadow 0 1px 2px rgba(0,0,0,0.15);
+
+ &:hover
+ background #fff
+ box-shadow 0 1px 2px rgba(0,0,0,0.15);
+ a
+ @extends .flex
+ padding: 1rem 0 1rem 1rem
+ width: 100% - 5rem
+
+
+ span
+ font-size: 13px
+
+ .main-body
+ padding: 0.1em 1em
+
+ ul
+ li
+ padding: 0.5rem 0.5rem;
+
+ a
+ .is-checked
+ border-bottom: 2px solid #2980b9;
+ border-right: 2px solid #2980b9;
+
+ span
+ padding: 0 0.5rem
+
+ .invite-people
+ padding-left 20px;
+ li
+ min-width: 500px;
+
+ ul.no-margin-bottom
+ margin-bottom: 0;
+
+ .bg-white
+ a
+ background #f7f7f7
+ &.is-checked
+ background #fff
+
+
+.option
+ @extends .flex
+ -webkit-border-radius: 3px;
+ border-radius: 3px;
+ background: #fff;
+ text-decoration: none;
+ -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.2);
+ box-shadow: 0 1px 2px rgba(0,0,0,0.2);
+ margin-top: 5px;
+ padding: 5px;
+
+.title
+ font-weight 700;
+ margin-bottom 0.5rem;
+.description
+ margin-bottom 0.5rem;
+.bg-white
+ background #f9fbfc;
+
+.form-control.has-error
+ border-color: #a94442;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+
+li.has-error
+ color #a94442
+ .form-group
+ .form-control
+ border-color: #a94442;
+ box-shadow: inset 0 1px 1px rgba(0,0,0,.075);
+
diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade
new file mode 100644
index 00000000..fb884056
--- /dev/null
+++ b/client/components/settings/settingHeader.jade
@@ -0,0 +1,21 @@
+template(name="settingHeaderBar")
+ h1.header-setting-menu
+ span {{_ 'admin-panel'}}
+
+ .setting-header-btns.left
+ unless isMiniScreen
+ unless isSandstorm
+ if currentUser
+ a.setting-header-btn.settings.active
+ i.fa(class="fa-cog")
+ span {{_ 'settings'}}
+//TODO
+// a.setting-header-btn.people
+// i.fa(class="fa-users")
+// span {{_ 'people'}}
+
+ else
+ a.setting-header-btn.js-log-in(
+ title="{{_ 'log-in'}}")
+ i.fa.fa-sign-in
+ span {{_ 'log-in'}}
diff --git a/client/components/settings/settingHeader.styl b/client/components/settings/settingHeader.styl
new file mode 100644
index 00000000..995ed26d
--- /dev/null
+++ b/client/components/settings/settingHeader.styl
@@ -0,0 +1,25 @@
+#header #header-main-bar .setting-header-btn
+ &.active,
+ &:hover:not(.is-disabled)
+ background: rgba(0, 0, 0, .15)
+ color: darken(white, 5%)
+ margin-left: 20px;
+ padding-right: 10px;
+ height: 28px;
+ font-size: 13px;
+ float: left;
+ overflow: hidden;
+ line-height: @height;
+ margin: 0 2px;
+
+ i.fa
+ float: left
+ display: block
+ line-height: 28px
+ color: darken(white, 5%)
+ margin: 0 10px
+
+ + span
+ display: inline-block
+ margin-top: 1px
+ margin-right: 10px \ No newline at end of file
diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade
index ad41e8aa..51b0888b 100644
--- a/client/components/users/userHeader.jade
+++ b/client/components/users/userHeader.jade
@@ -17,6 +17,8 @@ template(name="memberMenuPopup")
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
li: a.js-edit-notification {{_ 'editNotificationPopup-title'}}
+ if currentUser.isAdmin
+ li: a.js-go-setting(href='/setting') {{_ 'admin-panel'}}
hr
ul.pop-over-list
li: a.js-logout {{_ 'log-out'}}
diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js
index 98053ed1..73a11fc0 100644
--- a/client/components/users/userHeader.js
+++ b/client/components/users/userHeader.js
@@ -15,6 +15,9 @@ Template.memberMenuPopup.events({
AccountsTemplates.logout();
},
+ 'click .js-go-setting'() {
+ Popup.close();
+ },
});
Template.editProfilePopup.events({
diff --git a/config/accounts.js b/config/accounts.js
index 9ab26b33..51c0f49e 100644
--- a/config/accounts.js
+++ b/config/accounts.js
@@ -1,12 +1,21 @@
const passwordField = AccountsTemplates.removeField('password');
const emailField = AccountsTemplates.removeField('email');
+
AccountsTemplates.addFields([{
_id: 'username',
type: 'text',
displayName: 'username',
required: true,
minLength: 2,
-}, emailField, passwordField]);
+}, emailField, passwordField, {
+ _id: 'invitationcode',
+ type: 'text',
+ displayName: 'Invitation Code',
+ required: false,
+ minLength: 6,
+ errStr: 'Invitation code doesn\'t exist',
+ template: 'invitationCode',
+}]);
AccountsTemplates.configure({
defaultLayout: 'userFormsLayout',
@@ -48,9 +57,6 @@ AccountsTemplates.configureRoute('changePwd', {
});
if (Meteor.isServer) {
- if (process.env.MAIL_FROM) {
- Accounts.emailTemplates.from = process.env.MAIL_FROM;
- }
['resetPassword-subject', 'resetPassword-text', 'verifyEmail-subject', 'verifyEmail-text', 'enrollAccount-subject', 'enrollAccount-text'].forEach((str) => {
const [templateName, field] = str.split('-');
@@ -63,3 +69,4 @@ if (Meteor.isServer) {
};
});
}
+
diff --git a/config/router.js b/config/router.js
index 7194621b..72592bd6 100644
--- a/config/router.js
+++ b/config/router.js
@@ -99,6 +99,26 @@ FlowRouter.route('/import', {
},
});
+FlowRouter.route('/setting', {
+ name: 'setting',
+ triggersEnter: [
+ AccountsTemplates.ensureSignedIn,
+ () => {
+ Session.set('currentBoard', null);
+ Session.set('currentCard', null);
+
+ Filter.reset();
+ EscapeActions.executeAll();
+ },
+ ],
+ action() {
+ BlazeLayout.render('defaultLayout', {
+ headerBar: 'settingHeaderBar',
+ content: 'setting',
+ });
+ },
+});
+
FlowRouter.notFound = {
action() {
BlazeLayout.render('defaultLayout', { content: 'notFound' });
diff --git a/i18n/en.i18n.json b/i18n/en.i18n.json
index ad74a0e0..c57d9274 100644
--- a/i18n/en.i18n.json
+++ b/i18n/en.i18n.json
@@ -323,5 +323,26 @@
"welcome-board": "Welcome Board",
"welcome-list1": "Basics",
"welcome-list2": "Advanced",
- "what-to-do": "What do you want to do?"
-} \ No newline at end of file
+ "what-to-do": "What do you want to do?",
+ "admin-panel": "Admin Panel",
+ "system-setting": "System Setting",
+ "settings": "Settings",
+ "people": "People",
+ "registration": "Registration",
+ "disable-self-registration": "Disable Self-Registration",
+ "invite": "Invite",
+ "invite-people": "Invite People",
+ "to-boards": "To board(s)",
+ "email-addresses":"Email Addresses",
+ "smtp-host-description": "The address of the SMTP server that handles your emails.",
+ "smtp-port-description": "The port your SMTP server uses for outgoing emails.",
+ "smtp-host": "SMTP Host",
+ "smtp-port": "SMTP Port",
+ "smtp-username": "Username",
+ "smtp-password": "Password",
+ "send-from": "From",
+ "invitation-code": "Invitation Code",
+ "email-invite-register-subject": "__inviter__ sent you an invitation",
+ "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to Wekan for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.\n",
+ "error-invitation-code-not-exist": "Invitation code doesn't exist"
+}
diff --git a/models/invitationCodes.js b/models/invitationCodes.js
new file mode 100644
index 00000000..5761977a
--- /dev/null
+++ b/models/invitationCodes.js
@@ -0,0 +1,45 @@
+InvitationCodes = new Mongo.Collection('invitation_codes');
+
+InvitationCodes.attachSchema(new SimpleSchema({
+ code: {
+ type: String,
+ },
+ email: {
+ type: String,
+ unique: true,
+ regEx: SimpleSchema.RegEx.Email,
+ },
+ createdAt: {
+ type: Date,
+ denyUpdate: false,
+ },
+ // always be the admin if only one admin
+ authorId: {
+ type: String,
+ },
+ boardsToBeInvited: {
+ type: [String],
+ optional: true,
+ },
+ valid: {
+ type: Boolean,
+ defaultValue: true,
+ },
+}));
+
+InvitationCodes.helpers({
+ author(){
+ return Users.findOne(this.authorId);
+ },
+});
+
+// InvitationCodes.before.insert((userId, doc) => {
+ // doc.createdAt = new Date();
+ // doc.authorId = userId;
+// });
+
+if (Meteor.isServer) {
+ Boards.deny({
+ fetch: ['members'],
+ });
+}
diff --git a/models/settings.js b/models/settings.js
new file mode 100644
index 00000000..b9ff1b37
--- /dev/null
+++ b/models/settings.js
@@ -0,0 +1,116 @@
+Settings = new Mongo.Collection('settings');
+
+Settings.attachSchema(new SimpleSchema({
+ disableRegistration: {
+ type: Boolean,
+ },
+ 'mailServer.username': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.password': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.host': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.port': {
+ type: String,
+ optional: true,
+ },
+ 'mailServer.from': {
+ type: String,
+ optional: true,
+ defaultValue: 'Wekan',
+ },
+ createdAt: {
+ type: Date,
+ denyUpdate: true,
+ },
+ modifiedAt: {
+ type: Date,
+ },
+}));
+Settings.helpers({
+ mailUrl () {
+ const mailUrl = `smtp://${this.mailServer.username}:${this.mailServer.password}@${this.mailServer.host}:${this.mailServer.port}/`;
+ return mailUrl;
+ },
+});
+Settings.allow({
+ update(userId) {
+ const user = Users.findOne(userId);
+ return user && user.isAdmin;
+ },
+});
+
+Settings.before.update((userId, doc, fieldNames, modifier) => {
+ modifier.$set = modifier.$set || {};
+ modifier.$set.modifiedAt = new Date();
+});
+
+if (Meteor.isServer) {
+ Meteor.startup(() => {
+ const setting = Settings.findOne({});
+ if(!setting){
+ const now = new Date();
+ const defaultSetting = {disableRegistration: false, mailServer: {
+ username: '', password:'', host: '', port:'', from: '',
+ }, createdAt: now, modifiedAt: now};
+ Settings.insert(defaultSetting);
+ }
+ const newSetting = Settings.findOne();
+ process.env.MAIL_URL = newSetting.mailUrl();
+ Accounts.emailTemplates.from = newSetting.mailServer.from;
+ });
+
+ function getRandomNum (min, max) {
+ const range = max - min;
+ const rand = Math.random();
+ return (min + Math.round(rand * range));
+ }
+
+ function sendInvitationEmail (_id){
+ const icode = InvitationCodes.findOne(_id);
+ const author = Users.findOne(Meteor.userId());
+ try {
+ const params = {
+ email: icode.email,
+ inviter: Users.findOne(icode.authorId).username,
+ user: icode.email.split('@')[0],
+ icode: icode.code,
+ url: FlowRouter.url('sign-up'),
+ };
+ const lang = author.getLanguage();
+ Email.send({
+ to: icode.email,
+ from: Accounts.emailTemplates.from,
+ subject: TAPi18n.__('email-invite-register-subject', params, lang),
+ text: TAPi18n.__('email-invite-register-text', params, lang),
+ });
+ } catch (e) {
+ throw new Meteor.Error('email-fail', e.message);
+ }
+ }
+
+ Meteor.methods({
+ sendInvitation(emails, boards) {
+ check(emails, [String]);
+ check(boards, [String]);
+ const user = Users.findOne(Meteor.userId());
+ if(!user.isAdmin){
+ throw new Meteor.Error('not-allowed');
+ }
+ emails.forEach((email) => {
+ if (email && SimpleSchema.RegEx.Email.test(email)) {
+ const code = getRandomNum(100000, 999999);
+ InvitationCodes.insert({code, email, boardsToBeInvited: boards, createdAt: new Date(), authorId: Meteor.userId()}, function(err, _id){
+ if(!err && _id) sendInvitationEmail(_id);
+ });
+ }
+ });
+ },
+ });
+}
diff --git a/models/users.js b/models/users.js
index da2d02ee..06b84fa0 100644
--- a/models/users.js
+++ b/models/users.js
@@ -348,7 +348,7 @@ if (Meteor.isServer) {
if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf');
} else {
if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist');
-
+ if (Settings.findOne().disableRegistration) throw new Meteor.Error('error-user-notCreated');
// Set in lowercase email before creating account
const email = username.toLowerCase();
username = email.substring(0, posAt);
@@ -390,6 +390,28 @@ if (Meteor.isServer) {
return { username: user.username, email: user.emails[0].address };
},
});
+ Accounts.onCreateUser((options, user) => {
+ const userCount = Users.find().count();
+ if (userCount === 0){
+ user.isAdmin = true;
+ return user;
+ }
+ const disableRegistration = Settings.findOne().disableRegistration;
+ if (!disableRegistration) {
+ return user;
+ }
+
+ const iCode = options.profile.invitationcode | '';
+
+ const invitationCode = InvitationCodes.findOne({code: iCode, valid:true});
+ if (!invitationCode) {
+ throw new Meteor.Error('error-invitation-code-not-exist');
+ }else{
+ user.profile = {icode: options.profile.invitationcode};
+ }
+
+ return user;
+ });
}
if (Meteor.isServer) {
@@ -459,4 +481,25 @@ if (Meteor.isServer) {
});
});
}
+
+ Users.after.insert((userId, doc) => {
+
+ //invite user to corresponding boards
+ const disableRegistration = Settings.findOne().disableRegistration;
+ if (disableRegistration) {
+ const user = Users.findOne(doc._id);
+ const invitationCode = InvitationCodes.findOne({code: user.profile.icode, valid:true});
+ if (!invitationCode) {
+ throw new Meteor.Error('error-user-notCreated');
+ }else{
+ invitationCode.boardsToBeInvited.forEach((boardId) => {
+ const board = Boards.findOne(boardId);
+ board.addMember(doc._id);
+ });
+ user.profile = {invitedBoards: invitationCode.boardsToBeInvited};
+ InvitationCodes.update(invitationCode._id, {$set: {valid:false}});
+ }
+ }
+ });
}
+
diff --git a/server/publications/settings.js b/server/publications/settings.js
new file mode 100644
index 00000000..c2d9fdff
--- /dev/null
+++ b/server/publications/settings.js
@@ -0,0 +1,13 @@
+Meteor.publish('setting', () => {
+ return Settings.find({}, {fields:{disableRegistration: 1}});
+});
+
+Meteor.publish('mailServer', function () {
+ if (!Match.test(this.userId, String))
+ return [];
+ const user = Users.findOne(this.userId);
+ if(user && user.isAdmin){
+ return Settings.find({}, {fields: {mailServer: 1}});
+ }
+ return [];
+});
diff --git a/server/publications/users.js b/server/publications/users.js
index 4321e32b..4fd98e13 100644
--- a/server/publications/users.js
+++ b/server/publications/users.js
@@ -9,3 +9,11 @@ Meteor.publish('user-miniprofile', function(userId) {
},
});
});
+
+Meteor.publish('user-admin', function() {
+ return Meteor.users.find(this.userId, {
+ fields: {
+ isAdmin: 1,
+ },
+ });
+});