diff --git a/config/env/torrents.js b/config/env/torrents.js index c8b7ab83..8138e4b5 100644 --- a/config/env/torrents.js +++ b/config/env/torrents.js @@ -214,6 +214,7 @@ module.exports = { userInvitationExchange: {name: 'userInvitationExchange', enable: true}, adminRemoveHnrWarning: {name: 'adminRemoveHnrWarning', enable: true}, userRemoveHnrWarning: {name: 'userRemoveHnrWarning', enable: true}, + adminSendOfficialInvitation: {name: 'adminSendOfficialInvitation', enable: true}, userAnnounceData: {name: 'userAnnounceData', enable: true}, userScoreChange: {name: 'userScoreChange', enable: true}, diff --git a/config/lib/debug.js b/config/lib/debug.js index 84618a35..d40b56a1 100644 --- a/config/lib/debug.js +++ b/config/lib/debug.js @@ -13,7 +13,8 @@ var appConfig = config.meanTorrentConfig.app; */ module.exports.debug = function (obj) { if (appConfig.showDebugLog) { - console.log('[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + obj); + console.log('[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']'); + console.log(obj); } }; @@ -23,7 +24,8 @@ module.exports.debug = function (obj) { */ module.exports.debugGreen = function (obj) { if (appConfig.showDebugLog) { - console.log(chalk.green('[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + obj)); + console.log(chalk.green('[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']')); + console.log(obj); } }; @@ -33,7 +35,8 @@ module.exports.debugGreen = function (obj) { */ module.exports.debugRed = function (obj) { if (appConfig.showDebugLog) { - console.log(chalk.red('[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + obj)); + console.log(chalk.red('[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']')); + console.log(obj); } }; module.exports.debugError = function (obj) { @@ -46,7 +49,8 @@ module.exports.debugError = function (obj) { */ module.exports.debugBlue = function (obj) { if (appConfig.showDebugLog) { - console.log(chalk.blue('[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + obj)); + console.log(chalk.blue('[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']')); + console.log(obj); } }; @@ -56,7 +60,8 @@ module.exports.debugBlue = function (obj) { */ module.exports.debugYellow = function (obj) { if (appConfig.showDebugLog) { - console.log(chalk.yellow('[' + moment().format('YYYY-MM-DD HH:mm:ss') + '] ' + obj)); + console.log(chalk.yellow('[' + moment().format('YYYY-MM-DD HH:mm:ss') + ']')); + console.log(obj); } }; diff --git a/modules/core/client/app/trans-string-en.js b/modules/core/client/app/trans-string-en.js index 74962d1f..f8a09c0a 100644 --- a/modules/core/client/app/trans-string-en.js +++ b/modules/core/client/app/trans-string-en.js @@ -539,6 +539,10 @@ INVITATION_IS_EMPTY: 'There are no invitations available!', INVITATION_USED_IS_EMPTY: 'There are no used invitations!', + ADMIN_SEND_OFFICIAL_INVITATION: 'Send official invitation', + ADMIN_INVITATION_SUCCESSFULLY: 'Send official invitation successfully', + ADMIN_INVITATION_ERROR: 'Send official invitation failed', + //user message box MESSAGES_BOX: 'Messages Box', MESSAGES_SEND: 'Send Messages', diff --git a/modules/core/client/app/trans-string-zh.js b/modules/core/client/app/trans-string-zh.js index e503974c..19119eb6 100644 --- a/modules/core/client/app/trans-string-zh.js +++ b/modules/core/client/app/trans-string-zh.js @@ -539,6 +539,10 @@ INVITATION_IS_EMPTY: '没有可用的邀请函!', INVITATION_USED_IS_EMPTY: '没有发送过的邀请!', + ADMIN_SEND_OFFICIAL_INVITATION: '发送官方邀请函', + ADMIN_INVITATION_SUCCESSFULLY: '官方邀请函发送成功', + ADMIN_INVITATION_ERROR: '官方邀请函发送失败', + //user message box MESSAGES_BOX: '站内消息', MESSAGES_SEND: '发送消息', diff --git a/modules/core/client/less/btn-size.less b/modules/core/client/less/btn-size.less index 41c6f1c3..7c390739 100644 --- a/modules/core/client/less/btn-size.less +++ b/modules/core/client/less/btn-size.less @@ -27,3 +27,37 @@ .btn-width-200 { min-width: 120px !important; } + +.width-50 { + min-width: 50px !important; +} +.width-60 { + min-width: 60px !important; +} +.width-70 { + min-width: 70px !important; +} +.width-80 { + min-width: 80px !important; +} +.width-90 { + min-width: 90px !important; +} +.width-100 { + min-width: 100px !important; +} +.width-120 { + min-width: 120px !important; +} +.width-150 { + min-width: 150px !important; +} +.width-200 { + min-width: 200px !important; +} +.width-300 { + min-width: 300px !important; +} +.width-400 { + min-width: 400px !important; +} diff --git a/modules/invitations/client/controllers/admin-invitations.client.controller.js b/modules/invitations/client/controllers/admin-invitations.client.controller.js new file mode 100644 index 00000000..4272e904 --- /dev/null +++ b/modules/invitations/client/controllers/admin-invitations.client.controller.js @@ -0,0 +1,39 @@ +(function () { + 'use strict'; + + angular + .module('invitations.admin') + .controller('AdminInvitationController', AdminInvitationController); + + AdminInvitationController.$inject = ['$scope', '$state', 'Authentication', 'InvitationsService', 'NotifycationService', 'DebugConsoleService']; + + function AdminInvitationController($scope, $state, Authentication, InvitationsService, NotifycationService, mtDebug) { + var vm = this; + vm.user = Authentication.user; + vm.invitationFields = { + isOfficial: true + }; + /** + * If user is not signed in then redirect back home + */ + if (!Authentication.user) { + $state.go('authentication.signin'); + } + + /** + * sendOfficialInvitation + */ + vm.sendOfficialInvitation = function () { + if (vm.invitationFields.email) { + var invitation = new InvitationsService(vm.invitationFields); + + invitation.$official(function (res) { + mtDebug.info(res); + NotifycationService.showSuccessNotify('ADMIN_INVITATION_SUCCESSFULLY'); + }, function (res) { + NotifycationService.showErrorNotify(res.data.message, 'EXCHANGE_INVITATION_ERROR'); + }); + } + }; + } +}()); diff --git a/modules/invitations/client/services/invitations.client.service.js b/modules/invitations/client/services/invitations.client.service.js index a4be6949..8ca3de9d 100644 --- a/modules/invitations/client/services/invitations.client.service.js +++ b/modules/invitations/client/services/invitations.client.service.js @@ -24,6 +24,10 @@ countInvitations: { method: 'GET', url: '/api/invitations/count' + }, + official: { + method: 'POST', + url: '/api/invitations/official' } }); } diff --git a/modules/invitations/client/views/admin/official.client.view.html b/modules/invitations/client/views/admin/official.client.view.html index e69de29b..8d50a9de 100644 --- a/modules/invitations/client/views/admin/official.client.view.html +++ b/modules/invitations/client/views/admin/official.client.view.html @@ -0,0 +1,27 @@ +
+
+
+ +
+

{{'ADMIN_SEND_OFFICIAL_INVITATION' | translate}}

+
  • +
    + +
    +
    +
    + +
    + + +
    +
    +
    +
    + +
    diff --git a/modules/invitations/server/controllers/invitations.server.controller.js b/modules/invitations/server/controllers/invitations.server.controller.js index 72d669e9..c846fed9 100644 --- a/modules/invitations/server/controllers/invitations.server.controller.js +++ b/modules/invitations/server/controllers/invitations.server.controller.js @@ -7,6 +7,7 @@ var path = require('path'), config = require(path.resolve('./config/config')), mongoose = require('mongoose'), errorHandler = require(path.resolve('./modules/core/server/controllers/errors.server.controller')), + validator = require('validator'), nodemailer = require('nodemailer'), User = mongoose.model('User'), Invitation = mongoose.model('Invitation'), @@ -15,6 +16,15 @@ var path = require('path'), var smtpTransport = nodemailer.createTransport(config.mailer.options); var traceConfig = config.meanTorrentConfig.trace; +var mtDebug = require(path.resolve('./config/lib/debug')); + + +/** + * A Validation function for local strategy email + */ +var validateEmail = function (email) { + return validator.isEmail(email, {require_tld: false}); +}; /** * create a Invitation @@ -144,6 +154,7 @@ exports.update = function (req, res) { } else if (results[1] > 0) { return res.status(422).send({message: 'EMAIL_ALREADY_EXIST'}); } else { + //send invitation mail var httpTransport = 'http://'; if (config.secure && config.secure.ssl === true) { httpTransport = 'https://'; @@ -192,6 +203,108 @@ exports.update = function (req, res) { }); }; +/** + * official + * send official invitation + * @param req + * @param res + */ +exports.official = function (req, res) { + if (!validateEmail(req.body.email)) { + return res.status(422).send({ + message: 'ERROR: invalid email address!' + }); + } else { + + var countRegisteredEmail = function (callback) { + User.count({email: req.body.email}, function (err, count) { + if (err) { + callback(err, null); + } else { + callback(null, count); + } + }); + }; + var countInvitedEmail = function (callback) { + Invitation.count({to_email: req.body.email}, function (err, count) { + if (err) { + callback(err, null); + } else { + callback(null, count); + } + }); + }; + + async.parallel([countRegisteredEmail, countInvitedEmail], function (err, results) { + if (err) { + return res.status(422).send(err); + } else { + if (results[0] > 0) { + return res.status(422).send({message: 'EMAIL_ALREADY_REGISTERED'}); + } else if (results[1] > 0) { + return res.status(422).send({message: 'EMAIL_ALREADY_EXIST'}); + } else { + //write invitation data + var invitation = new Invitation(); + invitation.user = req.user; + invitation.token = req.user.randomAsciiString(32); + invitation.to_email = req.body.email; + invitation.status = 1; + invitation.invitedat = Date.now(); + invitation.expiresat = Date.now() + config.meanTorrentConfig.invite.expires; + invitation.isOfficial = true; + + invitation.save(function (err) { + if (err) { + return res.status(422).send({ + message: errorHandler.getErrorMessage(err) + }); + } else { + //create trace log + traceLogCreate(req, traceConfig.action.adminSendOfficialInvitation, { + to: req.body.email, + token: invitation.token + }); + } + }); + + //send invitation mail + var httpTransport = 'http://'; + if (config.secure && config.secure.ssl === true) { + httpTransport = 'https://'; + } + var baseUrl = req.app.get('domain') || httpTransport + req.headers.host; + res.render(path.resolve('modules/invitations/server/templates/invite-sign-up-email'), { + to_email: req.body.email, + name: req.user.displayName, + appName: config.app.title, + url: baseUrl + '/api/auth/invite/' + invitation.token, + hours: config.meanTorrentConfig.invite.expires / (60 * 60 * 1000) + }, function (err, emailHTML) { + if (err) { + return res.status(422).send({message: 'INVITE_MAIL_RENDER_FAILED'}); + } else { + var mailOptions = { + to: req.body.email, + from: config.mailer.from, + subject: config.app.title + ' invitation', + html: emailHTML + }; + smtpTransport.sendMail(mailOptions, function (err) { + if (err) { + return res.status(422).send({message: 'INVITE_MAIL_SEND_FAILED'}); + } else { + res.json(invitation); + } + }); + } + }); + } + } + }); + } +}; + /** * Delete an invitation */ diff --git a/modules/invitations/server/policies/invitations.server.policy.js b/modules/invitations/server/policies/invitations.server.policy.js index 1ca4e5ec..14d849f7 100644 --- a/modules/invitations/server/policies/invitations.server.policy.js +++ b/modules/invitations/server/policies/invitations.server.policy.js @@ -14,6 +14,12 @@ acl = new acl(new acl.memoryBackend()); exports.invokeRolesPolicies = function () { acl.allow( [ + { + roles: ['admin', 'oper'], + allows: [ + {resources: '/api/invitations/official', permissions: ['post']} + ] + }, { roles: ['admin', 'oper', 'user'], allows: [ diff --git a/modules/invitations/server/routes/invitations.server.routes.js b/modules/invitations/server/routes/invitations.server.routes.js index cbacfb9c..7ab909f7 100644 --- a/modules/invitations/server/routes/invitations.server.routes.js +++ b/modules/invitations/server/routes/invitations.server.routes.js @@ -17,6 +17,9 @@ module.exports = function (app) { app.route('/api/invitations/count').all(invitationsPolicy.isAllowed) .get(invitations.countInvitations); + app.route('/api/invitations/official').all(invitationsPolicy.isAllowed) + .post(invitations.official); + app.route('/api/invitations/:invitationId').all(invitationsPolicy.isAllowed) .put(invitations.update) .delete(invitations.delete); diff --git a/modules/messages/client/views/admin-send.client.view.html b/modules/messages/client/views/admin-send.client.view.html index a8cc6666..1f6b58cf 100644 --- a/modules/messages/client/views/admin-send.client.view.html +++ b/modules/messages/client/views/admin-send.client.view.html @@ -11,7 +11,7 @@
    -
    {{ 'MESSAGES_FIELD.TYPE' | translate}}:
    +
    {{ 'MESSAGES_FIELD.TYPE' | translate}}
    @@ -33,7 +33,7 @@
    -
    {{ 'MESSAGES_FIELD.CONTENT' | translate}}:
    +
    {{ 'MESSAGES_FIELD.CONTENT' | translate}}