From 1e06cc9f5a8c0eacaef308f3aa87d78d683e3137 Mon Sep 17 00:00:00 2001 From: OldHawk Date: Sat, 26 May 2018 12:58:22 +0800 Subject: [PATCH] feat(invitations): admin can present official invitations to user --- config/env/torrents.js | 15 +- modules/core/client/app/trans-string-en.js | 14 +- modules/core/client/app/trans-string-zh-tw.js | 14 +- modules/core/client/app/trans-string-zh.js | 14 +- modules/core/client/less/mt.less | 16 +- .../admin-invitations.client.controller.js | 2 +- .../invitations.client.controller.js | 96 +++++++- .../views/admin/official.client.view.html | 2 +- .../client/views/detail.client.view.html | 209 ++++++++++++------ .../invitations.server.controller.js | 7 +- .../server/models/invitation.server.model.js | 5 + .../admin/user.client.controller.js | 43 ++++ modules/users/client/less/user.less | 7 + .../client/services/users.client.service.js | 11 + .../views/admin/view-user.client.view.html | 42 +++- .../controllers/admin.server.controller.js | 39 ++++ .../server/policies/admin.server.policy.js | 3 +- .../server/routes/admin.server.routes.js | 2 + 18 files changed, 451 insertions(+), 90 deletions(-) create mode 100644 modules/users/client/less/user.less diff --git a/config/env/torrents.js b/config/env/torrents.js index 10344861..84c44640 100644 --- a/config/env/torrents.js +++ b/config/env/torrents.js @@ -397,13 +397,19 @@ module.exports = { * if exceed the expiration time, the invite send qualifications will invalid and user also can not signin(register). * @expires_str: string desc of @expires * @banUserInviter: settings whether ban the user`s inviter when a user banned + * + * @official: settings of official invitations + * @presentDays: settings of present official available days list */ invite: { openInvite: true, scoreExchange: 10000, expires: 60 * 60 * 1000 * 24, expires_str: '24h', - banUserInviter: true + banUserInviter: true, + official: { + presentDays: [1, 10, 30, 90, 180] + } }, /** @@ -699,6 +705,7 @@ module.exports = { OperDeleteBackupFiles: {name: 'OperDeleteBackupFiles', enable: true}, userInvitationExchange: {name: 'userInvitationExchange', enable: true}, + adminPresentInvitations: {name: 'adminPresentInvitations', enable: true}, adminRemoveHnrWarning: {name: 'adminRemoveHnrWarning', enable: true}, userRemoveHnrWarning: {name: 'userRemoveHnrWarning', enable: true}, userSendInvitation: {name: 'userSendInvitation', enable: true}, @@ -1329,7 +1336,8 @@ module.exports = { * @collectionsListPerPage: movie collections list page settings * @backupFilesListPerPage: system backup files list page settings * @torrentPeersListPerPage: torrent detail seeder & leecher users list page settings - * @invitationsListPerPage: official invitations list page settings + * @officialInvitationsListPerPage: official invitations list page settings + * @userInvitationsListPerPage: users invitations list page settings * * @uploaderUserListPerPage: admin management uploader access list page settings * @messageBoxListPerPage: message box list page settings @@ -1364,7 +1372,8 @@ module.exports = { collectionsListPerPage: 9, backupFilesListPerPage: 20, torrentPeersListPerPage: 20, - invitationsListPerPage: 20, + officialInvitationsListPerPage: 20, + userInvitationsListPerPage: 10, uploaderUserListPerPage: 20, messageBoxListPerPage: 10, diff --git a/modules/core/client/app/trans-string-en.js b/modules/core/client/app/trans-string-en.js index 1ffebc95..99f8e51a 100644 --- a/modules/core/client/app/trans-string-en.js +++ b/modules/core/client/app/trans-string-en.js @@ -635,6 +635,14 @@ SET_DOWNLOADED_SUCCESSFULLY: 'Add user downloaded successfully', SET_DOWNLOADED_FAILED: 'Add user downloaded failed', + PRESENT_INVITATIONS_TITLE: 'Present invitations', + PRESENT_INVITATIONS_NUMBERS: 'Invitation numbers:', + PRESENT_INVITATIONS_DAYS: 'Available days:', + BTN_PRESENT: 'Present', + BTN_PRESENT_INVITATIONS: 'Present invitations', + PRESENT_INVITATIONS_SUCCESSFULLY: 'Present invitations successfully', + PRESENT_INVITATIONS_FAILED: 'Present invitations failed', + ANNOUNCE_EDIT_TIP: 'Upload a torrent file,
automatic replacement Announce URL & Comment', ANNOUNCE_COMMENT: 'Announce Comment', @@ -1065,12 +1073,16 @@ TITLE_OPERATION: 'Operation', TITLE_SENDER: 'Sender', TITLE_TOKEN: 'Token', + TITLE_TYPE: 'Type', TITLE_EMAIL: 'Email', TITLE_SEND: 'Send an invitation', TITLE_STATUS: 'Status', TITLE_STATUS_REGED: 'signed', TITLE_STATUS_UNREGED: 'unsigned', - TITLE_STATUS_EXPIRED: 'expired' + TITLE_STATUS_EXPIRED: 'expired', + TYPE_EXCHANGE: 'exchange', + TYPE_PRESENT: 'present', + TYPE_OFFICIAL: 'official' }, GET_INVITATIONS_ERROR: 'Get invitations failed', BUTTON_INVITE: 'Send', diff --git a/modules/core/client/app/trans-string-zh-tw.js b/modules/core/client/app/trans-string-zh-tw.js index 3e44fcb2..57248cd4 100644 --- a/modules/core/client/app/trans-string-zh-tw.js +++ b/modules/core/client/app/trans-string-zh-tw.js @@ -635,6 +635,14 @@ SET_DOWNLOADED_SUCCESSFULLY: '新增用戶下載量成功', SET_DOWNLOADED_FAILED: '新增用戶下載量成功', + PRESENT_INVITATIONS_TITLE: '贈送邀請', + PRESENT_INVITATIONS_NUMBERS: '邀請數量:', + PRESENT_INVITATIONS_DAYS: '有效天數:', + BTN_PRESENT: '贈送', + BTN_PRESENT_INVITATIONS: '贈送邀請', + PRESENT_INVITATIONS_SUCCESSFULLY: '贈送邀請成功', + PRESENT_INVITATIONS_FAILED: '贈送邀請失敗', + ANNOUNCE_EDIT_TIP: '上傳一個種子檔案,
自動替換 Announce URLComment', ANNOUNCE_COMMENT: 'Announce Comment', @@ -1065,12 +1073,16 @@ TITLE_OPERATION: '操作', TITLE_SENDER: '傳送人', TITLE_TOKEN: '邀請碼', + TITLE_TYPE: '類型', TITLE_EMAIL: '郵箱', TITLE_SEND: '傳送邀請', TITLE_STATUS: '狀態', TITLE_STATUS_REGED: '已註冊', TITLE_STATUS_UNREGED: '未註冊', - TITLE_STATUS_EXPIRED: '已失效' + TITLE_STATUS_EXPIRED: '已失效', + TYPE_EXCHANGE: '積分兌換', + TYPE_PRESENT: '官方贈送', + TYPE_OFFICIAL: '官方邀請' }, GET_INVITATIONS_ERROR: '獲取邀請列表失敗', BUTTON_INVITE: '傳送', diff --git a/modules/core/client/app/trans-string-zh.js b/modules/core/client/app/trans-string-zh.js index 12f89edd..f1e2c988 100644 --- a/modules/core/client/app/trans-string-zh.js +++ b/modules/core/client/app/trans-string-zh.js @@ -635,6 +635,14 @@ SET_DOWNLOADED_SUCCESSFULLY: '添加用户下载量成功', SET_DOWNLOADED_FAILED: '添加用户下载量成功', + PRESENT_INVITATIONS_TITLE: '赠送邀请', + PRESENT_INVITATIONS_NUMBERS: '邀请数量:', + PRESENT_INVITATIONS_DAYS: '有效天数:', + BTN_PRESENT: '赠送', + BTN_PRESENT_INVITATIONS: '赠送邀请', + PRESENT_INVITATIONS_SUCCESSFULLY: '赠送邀请成功', + PRESENT_INVITATIONS_FAILED: '赠送邀请失败', + ANNOUNCE_EDIT_TIP: '上传一个种子文件,
自动替换 Announce URLComment', ANNOUNCE_COMMENT: 'Announce Comment', @@ -1065,12 +1073,16 @@ TITLE_OPERATION: '操作', TITLE_SENDER: '发送人', TITLE_TOKEN: '邀请码', + TITLE_TYPE: '类型', TITLE_EMAIL: '邮箱', TITLE_SEND: '发送邀请', TITLE_STATUS: '状态', TITLE_STATUS_REGED: '已注册', TITLE_STATUS_UNREGED: '未注册', - TITLE_STATUS_EXPIRED: '已失效' + TITLE_STATUS_EXPIRED: '已失效', + TYPE_EXCHANGE: '积分兑换', + TYPE_PRESENT: '官方赠送', + TYPE_OFFICIAL: '官方邀请' }, GET_INVITATIONS_ERROR: '获取邀请列表失败', BUTTON_INVITE: '发送', diff --git a/modules/core/client/less/mt.less b/modules/core/client/less/mt.less index eb4ac733..bfebb6e9 100644 --- a/modules/core/client/less/mt.less +++ b/modules/core/client/less/mt.less @@ -14,8 +14,11 @@ body { background-color: @mt-body-background-color; } -.ui-notification{ - width: 400px; +.ui-notification { + width: 300px; + .message { + word-break: break-all; + } } .social-list { @@ -2005,6 +2008,15 @@ body { } } +.set-invitations-popover { + max-width: 250px; + width: 250px; + height: auto; + input { + max-width: 100px; + } +} + .signature-input { font-size: 0.9em; background-color: #f5f5f5; diff --git a/modules/invitations/client/controllers/admin-invitations.client.controller.js b/modules/invitations/client/controllers/admin-invitations.client.controller.js index ce81abf7..ffa2fa66 100644 --- a/modules/invitations/client/controllers/admin-invitations.client.controller.js +++ b/modules/invitations/client/controllers/admin-invitations.client.controller.js @@ -31,7 +31,7 @@ */ vm.buildPager = function () { vm.pagedItems = []; - vm.itemsPerPage = vm.itemsPerPageConfig.invitationsListPerPage; + vm.itemsPerPage = vm.itemsPerPageConfig.officialInvitationsListPerPage; vm.currentPage = 1; vm.figureOutItemsToDisplay(); }; diff --git a/modules/invitations/client/controllers/invitations.client.controller.js b/modules/invitations/client/controllers/invitations.client.controller.js index 46e475ed..18fd27d6 100644 --- a/modules/invitations/client/controllers/invitations.client.controller.js +++ b/modules/invitations/client/controllers/invitations.client.controller.js @@ -6,16 +6,17 @@ .controller('InviteController', InviteController); InviteController.$inject = ['$scope', '$state', '$translate', '$timeout', 'Authentication', '$window', 'MeanTorrentConfig', 'NotifycationService', - 'InvitationsService', '$rootScope', 'moment']; + 'InvitationsService', '$rootScope', 'moment', 'DebugConsoleService', '$filter']; function InviteController($scope, $state, $translate, $timeout, Authentication, $window, MeanTorrentConfig, NotifycationService, - InvitationsService, $rootScope, moment) { + InvitationsService, $rootScope, moment, mtDebug, $filter) { var vm = this; vm.inviteConfig = MeanTorrentConfig.meanTorrentConfig.invite; vm.signConfig = MeanTorrentConfig.meanTorrentConfig.sign; vm.user = Authentication.user; vm.announce = MeanTorrentConfig.meanTorrentConfig.announce; vm.hnrConfig = MeanTorrentConfig.meanTorrentConfig.hitAndRun; + vm.itemsPerPageConfig = MeanTorrentConfig.meanTorrentConfig.itemsPerPage; vm.invitePopover = { title: 'INVITATION.TITLE_SEND', @@ -35,17 +36,108 @@ vm.getMyInvitations(); }); + /** + * myBuildPager + */ + vm.myBuildPager = function () { + vm.myPagedItems = []; + vm.myItemsPerPage = vm.itemsPerPageConfig.userInvitationsListPerPage; + vm.myCurrentPage = 1; + vm.myFigureOutItemsToDisplay(); + }; + + /** + * myFigureOutItemsToDisplay + */ + vm.myFigureOutItemsToDisplay = function (callback) { + vm.myFilteredItems = vm.my_invitations; + vm.myFilterLength = vm.myFilteredItems.length; + var begin = ((vm.myCurrentPage - 1) * vm.myItemsPerPage); + var end = begin + vm.myItemsPerPage; + vm.myPagedItems = vm.myFilteredItems.slice(begin, end); + + if (callback) callback(); + }; + + /** + * myPageChanged + */ + vm.myPageChanged = function () { + var element = angular.element('#top_of_my_invitations_list'); + console.log(element); + + $('.tb-my').fadeTo(100, 0.01, function () { + vm.myFigureOutItemsToDisplay(function () { + $timeout(function () { + $('.tb-my').fadeTo(400, 1, function () { + console.log(element[0].offsetTop); + //window.scrollTo(0, element[0].offsetTop - 60); + $('html,body').animate({scrollTop: element[0].offsetTop - 60}, 200); + }); + }, 100); + }); + }); + }; + + /** + * buildPager + */ + vm.buildPager = function () { + vm.pagedItems = []; + vm.itemsPerPage = vm.itemsPerPageConfig.userInvitationsListPerPage; + vm.currentPage = 1; + vm.figureOutItemsToDisplay(); + }; + + /** + * figureOutItemsToDisplay + */ + vm.figureOutItemsToDisplay = function (callback) { + vm.filteredItems = $filter('filter')(vm.used_invitations, { + $: vm.search + }); + vm.filterLength = vm.filteredItems.length; + var begin = ((vm.currentPage - 1) * vm.itemsPerPage); + var end = begin + vm.itemsPerPage; + vm.pagedItems = vm.filteredItems.slice(begin, end); + + if (callback) callback(); + }; + + /** + * pageChanged + */ + vm.pageChanged = function () { + var element = angular.element('#top_of_invitations_list'); + console.log(element); + $('.tb-official').fadeTo(100, 0.01, function () { + vm.figureOutItemsToDisplay(function () { + $timeout(function () { + $('.tb-official').fadeTo(400, 1, function () { + //window.scrollTo(0, element[0].offsetTop - 60); + console.log(element[0].offsetTop); + $('html,body').animate({scrollTop: element[0].offsetTop - 60}, 200); + }); + }, 100); + }); + }); + }; + /** * getMyInvitations */ vm.getMyInvitations = function () { InvitationsService.get({}, function (items) { + mtDebug.info(items); vm.my_invitations = items.my_invitations; vm.used_invitations = items.used_invitations; angular.forEach(vm.my_invitations, function (i) { vm.invitePopover.items.push({isOpen: false}); }); + + vm.myBuildPager(); + vm.buildPager(); }, function (res) { NotifycationService.showErrorNotify('GET_INVITATIONS_ERROR'); }); diff --git a/modules/invitations/client/views/admin/official.client.view.html b/modules/invitations/client/views/admin/official.client.view.html index d3184a92..751d50cd 100644 --- a/modules/invitations/client/views/admin/official.client.view.html +++ b/modules/invitations/client/views/admin/official.client.view.html @@ -47,7 +47,7 @@
+ ng-change="vm.buildPager()"/>
diff --git a/modules/invitations/client/views/detail.client.view.html b/modules/invitations/client/views/detail.client.view.html index b3519b0d..39645a48 100644 --- a/modules/invitations/client/views/detail.client.view.html +++ b/modules/invitations/client/views/detail.client.view.html @@ -8,7 +8,7 @@ -
+
@@ -25,44 +25,77 @@

{{ 'INVITATION_IS_EMPTY' | translate }}

-
- - - - - - - - - - - - - - - - - - -
{{'INVITATION.TITLE_TOKEN' | translate}}{{'INVITATION.TITLE_CREATEDAT' | translate}}{{'INVITATION.TITLE_EXPIRESAT' | translate}}{{'INVITATION.TITLE_OPERATION' | translate}}
{{$index+1}} - {{r.token}}{{r.createdat | date:'yyyy-MM-dd HH:mm:ss'}}{{r.expiresat | date:'yyyy-MM-dd HH:mm:ss'}} - -
+
+
+
+
+
    +
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
{{'INVITATION.TITLE_TOKEN' | translate}}{{'INVITATION.TITLE_TYPE' | translate}}{{'INVITATION.TITLE_CREATEDAT' | translate}}{{'INVITATION.TITLE_EXPIRESAT' | translate}}{{'INVITATION.TITLE_OPERATION' | translate}}
{{$index+1}} + + {{r.token}} + + {{'INVITATION.TYPE_'+r.type.toUpperCase() | translate}} + + {{r.createdat | date:'yyyy-MM-dd HH:mm:ss'}}{{r.expiresat | date:'yyyy-MM-dd HH:mm:ss'}} + +
+
+ +
+
    +
+
-
+
@@ -79,45 +112,75 @@

{{ 'INVITATION_USED_IS_EMPTY' | translate }}

-
- - - - - - - - - - - - - - - - - - - - - - - - -
{{'INVITATION.TITLE_TOKEN' | translate}}{{'INVITATION.TITLE_EMAIL' | translate}}{{'INVITATION.TITLE_INVITEDAT' | translate}}{{'INVITATION.TITLE_SIGNUPAT' | translate}}{{'INVITATION.TITLE_SIGNUPID' | translate}}{{'INVITATION.TITLE_STATUS' | translate}}
{{$index+1}}{{r.token}}{{r.to_email}}{{r.invitedat | date:'yyyy-MM-dd HH:mm:ss'}}{{r.registeredat | date:'yyyy-MM-dd HH:mm:ss'}} - - - - - - - {{vm.getInvitationStatus(r);}} -
+
+
+
+
+
    +
+
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
{{'INVITATION.TITLE_TOKEN' | translate}}{{'INVITATION.TITLE_EMAIL' | translate}}{{'INVITATION.TITLE_INVITEDAT' | translate}}{{'INVITATION.TITLE_SIGNUPAT' | translate}}{{'INVITATION.TITLE_SIGNUPID' | translate}}{{'INVITATION.TITLE_STATUS' | translate}}
{{$index+1}}{{r.token}}{{r.to_email}}{{r.invitedat | date:'yyyy-MM-dd HH:mm:ss'}}{{r.registeredat | date:'yyyy-MM-dd HH:mm:ss'}} + + + + + + + {{vm.getInvitationStatus(r);}} +
+
+ +
+
    +
+
diff --git a/modules/invitations/server/controllers/invitations.server.controller.js b/modules/invitations/server/controllers/invitations.server.controller.js index 7461a9c9..29174520 100644 --- a/modules/invitations/server/controllers/invitations.server.controller.js +++ b/modules/invitations/server/controllers/invitations.server.controller.js @@ -131,7 +131,7 @@ exports.list = function (req, res) { expiresat: {$gt: Date.now()} }) .sort('createdat') - .populate('user', '-salt -password') + .populate('user', '-salt -password -followers -following -leeched_ip -signed_ip -signature') .exec(function (err, invitations) { if (err) { callback(err, null); @@ -147,8 +147,8 @@ exports.list = function (req, res) { status: {$gt: 0} }) .sort('invitedat') - .populate('user', '-salt -password') - .populate('to_user', '-salt -password') + .populate('user', '-salt -password -followers -following -leeched_ip -signed_ip -signature') + .populate('to_user', '-salt -password -followers -following -leeched_ip -signed_ip -signature') .exec(function (err, invitations) { if (err) { callback(err, null); @@ -308,6 +308,7 @@ exports.sendOfficial = function (req, res) { invitation.invitedat = Date.now(); invitation.expiresat = Date.now() + config.meanTorrentConfig.invite.expires; invitation.isOfficial = true; + invitation.type = 'official'; //send invitation mail var lang = common.getRequestLanguage(req); diff --git a/modules/invitations/server/models/invitation.server.model.js b/modules/invitations/server/models/invitation.server.model.js index 3ea9add8..3961725e 100644 --- a/modules/invitations/server/models/invitation.server.model.js +++ b/modules/invitations/server/models/invitation.server.model.js @@ -26,6 +26,11 @@ var InvitationSchema = new Schema({ trim: true, default: '' }, + type: { + type: String, + enum: ['exchange', 'present', 'official'], //'exchange' is from user`s score, 'present' is from official, 'official' is official invitations + default: 'exchange' + }, status: { type: Number, default: 0 //0 is unregistered invitation, 1 already invite friend, 2 is already registered diff --git a/modules/users/client/controllers/admin/user.client.controller.js b/modules/users/client/controllers/admin/user.client.controller.js index 1d10975a..c8d6f7bb 100644 --- a/modules/users/client/controllers/admin/user.client.controller.js +++ b/modules/users/client/controllers/admin/user.client.controller.js @@ -27,6 +27,7 @@ vm.isContextUserSelf = isContextUserSelf; vm.scoreLevelData = ScoreLevelService.getScoreLevelJson(vm.user.score); vm.inputLengthConfig = MeanTorrentConfig.meanTorrentConfig.inputLength; + vm.inviteConfig = MeanTorrentConfig.meanTorrentConfig.invite; vm.searchTags = []; vm.maker = {}; @@ -49,7 +50,18 @@ isOpen: false }; + vm.presentInvitationsPopover = { + title: 'PRESENT_INVITATIONS_TITLE', + templateUrl: 'present-invitations.html', + label_numbers: 'PRESENT_INVITATIONS_NUMBERS', + label_days: 'PRESENT_INVITATIONS_DAYS', + isOpen: false, + numbers: 1, + days: 1 + }; + mtDebug.info(vm.user); + /** * remove * @param user @@ -410,5 +422,36 @@ NotifycationService.showErrorNotify(response.data.message, 'SET_DOWNLOADED_FAILED'); } }; + + /** + * presentInvitations + */ + vm.presentInvitations = function () { + if (isNaN(vm.presentInvitationsPopover.numbers)) { + return false; + } else if (vm.presentInvitationsPopover.numbers < 0) { + return false; + } else { + var user = vm.user; + AdminService.presentUserInvitations({ + userId: user._id, + numbers: vm.presentInvitationsPopover.numbers, + days: vm.presentInvitationsPopover.days + }) + .then(onSuccess) + .catch(onError); + + vm.presentInvitationsPopover.isOpen = false; + } + + function onSuccess(response) { + vm.user = response; + NotifycationService.showSuccessNotify('PRESENT_INVITATIONS_SUCCESSFULLY'); + } + + function onError(response) { + NotifycationService.showErrorNotify(response.data.message, 'PRESENT_INVITATIONS_FAILED'); + } + }; } }()); diff --git a/modules/users/client/less/user.less b/modules/users/client/less/user.less new file mode 100644 index 00000000..ebe41977 --- /dev/null +++ b/modules/users/client/less/user.less @@ -0,0 +1,7 @@ +@import (reference) "../../../core/client/less/mt-var.less"; + +.user-cmd { + margin-bottom: 30px; + padding-bottom: 20px; + border-bottom: solid 1px #eee; +} \ No newline at end of file diff --git a/modules/users/client/services/users.client.service.js b/modules/users/client/services/users.client.service.js index d4461708..0f4e6b93 100644 --- a/modules/users/client/services/users.client.service.js +++ b/modules/users/client/services/users.client.service.js @@ -318,6 +318,13 @@ method: 'GET', url: '/api/users/uploaderList', cache: usersCache + }, + presentInvitations: { + method: 'PUT', + url: '/api/users/:userId/presentInvitations', + params: { + userId: '@userId' + } } }); @@ -351,7 +358,11 @@ }, getUploaderList: function (params) { return this.uploaderList(params).$promise; + }, + presentUserInvitations: function (params) { + return this.presentInvitations(params).$promise; } + }); return Users; diff --git a/modules/users/client/views/admin/view-user.client.view.html b/modules/users/client/views/admin/view-user.client.view.html index 29a495f4..f25107ac 100644 --- a/modules/users/client/views/admin/view-user.client.view.html +++ b/modules/users/client/views/admin/view-user.client.view.html @@ -19,6 +19,23 @@
+
+
+
+ +
+
+
+