mirror of
https://github.com/wekan/wekan.git
synced 2025-12-17 16:00:13 +01:00
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({});
This commit is contained in:
commit
cbc3c8224f
19 changed files with 658 additions and 8 deletions
|
|
@ -119,6 +119,8 @@
|
||||||
"allowIsBoardMember": true,
|
"allowIsBoardMember": true,
|
||||||
"allowIsBoardMemberByCard": true,
|
"allowIsBoardMemberByCard": true,
|
||||||
"Emoji": true,
|
"Emoji": true,
|
||||||
"Checklists": true
|
"Checklists": true,
|
||||||
|
"Settings": true,
|
||||||
|
"InvitationCodes": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
Meteor.subscribe('boards');
|
Meteor.subscribe('boards');
|
||||||
|
Meteor.subscribe('setting');
|
||||||
|
Meteor.subscribe('user-admin');
|
||||||
|
|
||||||
BlazeLayout.setRoot('body');
|
BlazeLayout.setRoot('body');
|
||||||
|
|
||||||
|
|
|
||||||
5
client/components/settings/invitationCode.jade
Normal file
5
client/components/settings/invitationCode.jade
Normal file
|
|
@ -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'}}")
|
||||||
6
client/components/settings/invitationCode.js
Normal file
6
client/components/settings/invitationCode.js
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
Template.invitationCode.onRendered(() => {
|
||||||
|
const disableRegistration = Settings.findOne().disableRegistration;
|
||||||
|
if(!disableRegistration){
|
||||||
|
$('#invitationcode').hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
71
client/components/settings/settingBody.jade
Normal file
71
client/components/settings/settingBody.jade
Normal file
|
|
@ -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
|
||||||
128
client/components/settings/settingBody.js
Normal file
128
client/components/settings/settingBody.js
Normal file
|
|
@ -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');
|
||||||
112
client/components/settings/settingBody.styl
Normal file
112
client/components/settings/settingBody.styl
Normal file
|
|
@ -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);
|
||||||
|
|
||||||
21
client/components/settings/settingHeader.jade
Normal file
21
client/components/settings/settingHeader.jade
Normal file
|
|
@ -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'}}
|
||||||
25
client/components/settings/settingHeader.styl
Normal file
25
client/components/settings/settingHeader.styl
Normal file
|
|
@ -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
|
||||||
|
|
@ -17,6 +17,8 @@ template(name="memberMenuPopup")
|
||||||
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
|
li: a.js-change-password {{_ 'changePasswordPopup-title'}}
|
||||||
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
|
li: a.js-change-language {{_ 'changeLanguagePopup-title'}}
|
||||||
li: a.js-edit-notification {{_ 'editNotificationPopup-title'}}
|
li: a.js-edit-notification {{_ 'editNotificationPopup-title'}}
|
||||||
|
if currentUser.isAdmin
|
||||||
|
li: a.js-go-setting(href='/setting') {{_ 'admin-panel'}}
|
||||||
hr
|
hr
|
||||||
ul.pop-over-list
|
ul.pop-over-list
|
||||||
li: a.js-logout {{_ 'log-out'}}
|
li: a.js-logout {{_ 'log-out'}}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ Template.memberMenuPopup.events({
|
||||||
|
|
||||||
AccountsTemplates.logout();
|
AccountsTemplates.logout();
|
||||||
},
|
},
|
||||||
|
'click .js-go-setting'() {
|
||||||
|
Popup.close();
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Template.editProfilePopup.events({
|
Template.editProfilePopup.events({
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,21 @@
|
||||||
const passwordField = AccountsTemplates.removeField('password');
|
const passwordField = AccountsTemplates.removeField('password');
|
||||||
const emailField = AccountsTemplates.removeField('email');
|
const emailField = AccountsTemplates.removeField('email');
|
||||||
|
|
||||||
AccountsTemplates.addFields([{
|
AccountsTemplates.addFields([{
|
||||||
_id: 'username',
|
_id: 'username',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
displayName: 'username',
|
displayName: 'username',
|
||||||
required: true,
|
required: true,
|
||||||
minLength: 2,
|
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({
|
AccountsTemplates.configure({
|
||||||
defaultLayout: 'userFormsLayout',
|
defaultLayout: 'userFormsLayout',
|
||||||
|
|
@ -48,9 +57,6 @@ AccountsTemplates.configureRoute('changePwd', {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Meteor.isServer) {
|
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) => {
|
['resetPassword-subject', 'resetPassword-text', 'verifyEmail-subject', 'verifyEmail-text', 'enrollAccount-subject', 'enrollAccount-text'].forEach((str) => {
|
||||||
const [templateName, field] = str.split('-');
|
const [templateName, field] = str.split('-');
|
||||||
|
|
@ -63,3 +69,4 @@ if (Meteor.isServer) {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = {
|
FlowRouter.notFound = {
|
||||||
action() {
|
action() {
|
||||||
BlazeLayout.render('defaultLayout', { content: 'notFound' });
|
BlazeLayout.render('defaultLayout', { content: 'notFound' });
|
||||||
|
|
|
||||||
|
|
@ -323,5 +323,26 @@
|
||||||
"welcome-board": "Welcome Board",
|
"welcome-board": "Welcome Board",
|
||||||
"welcome-list1": "Basics",
|
"welcome-list1": "Basics",
|
||||||
"welcome-list2": "Advanced",
|
"welcome-list2": "Advanced",
|
||||||
"what-to-do": "What do you want to do?"
|
"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"
|
||||||
}
|
}
|
||||||
45
models/invitationCodes.js
Normal file
45
models/invitationCodes.js
Normal file
|
|
@ -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'],
|
||||||
|
});
|
||||||
|
}
|
||||||
116
models/settings.js
Normal file
116
models/settings.js
Normal file
|
|
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -348,7 +348,7 @@ if (Meteor.isServer) {
|
||||||
if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf');
|
if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf');
|
||||||
} else {
|
} else {
|
||||||
if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist');
|
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
|
// Set in lowercase email before creating account
|
||||||
const email = username.toLowerCase();
|
const email = username.toLowerCase();
|
||||||
username = email.substring(0, posAt);
|
username = email.substring(0, posAt);
|
||||||
|
|
@ -390,6 +390,28 @@ if (Meteor.isServer) {
|
||||||
return { username: user.username, email: user.emails[0].address };
|
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) {
|
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}});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
13
server/publications/settings.js
Normal file
13
server/publications/settings.js
Normal file
|
|
@ -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 [];
|
||||||
|
});
|
||||||
|
|
@ -9,3 +9,11 @@ Meteor.publish('user-miniprofile', function(userId) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Meteor.publish('user-admin', function() {
|
||||||
|
return Meteor.users.find(this.userId, {
|
||||||
|
fields: {
|
||||||
|
isAdmin: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue