feat(follow): oper admin can list user`s follow detail

This commit is contained in:
OldHawk
2018-01-06 16:32:42 +08:00
parent 32a936fd15
commit 92c2e88ce9
10 changed files with 570 additions and 5 deletions

View File

@@ -97,6 +97,30 @@
pageTitle: 'PAGETITLE.MY_FOLLOWING'
}
})
.state('follow.userFollowers', {
url: '/:userId/followers',
templateUrl: '/modules/users/client/views/follow/user-followers.client.view.html',
controller: 'UserFollowersController',
controllerAs: 'vm',
resolve: {
userResolve: getUser
},
data: {
pageTitle: 'PAGETITLE.USER_FOLLOWERS'
}
})
.state('follow.userFollowing', {
url: '/:userId/following',
templateUrl: '/modules/users/client/views/follow/user-following.client.view.html',
controller: 'UserFollowingController',
controllerAs: 'vm',
resolve: {
userResolve: getUser
},
data: {
pageTitle: 'PAGETITLE.USER_FOLLOWING'
}
})
.state('status', {
abstract: true,
url: '/status',
@@ -246,5 +270,13 @@
pageTitle: 'PAGETITLE.PASSWORD_RESET'
}
});
getUser.$inject = ['$stateParams', 'AdminService'];
function getUser($stateParams, AdminService) {
return AdminService.get({
userId: $stateParams.userId
}).$promise;
}
}
}());

View File

@@ -0,0 +1,142 @@
(function () {
'use strict';
angular
.module('users')
.controller('UserFollowersController', UserFollowersController);
UserFollowersController.$inject = ['$scope', '$rootScope', '$state', '$timeout', '$translate', 'Authentication', 'UsersService', 'ScoreLevelService', 'MeanTorrentConfig', 'DebugConsoleService',
'NotifycationService', 'userResolve'];
function UserFollowersController($scope, $rootScope, $state, $timeout, $translate, Authentication, UsersService, ScoreLevelService, MeanTorrentConfig, mtDebug,
NotifycationService, userResolve) {
var vm = this;
vm.me = Authentication.user;
vm.user = userResolve;
vm.scoreLevelData = ScoreLevelService.getScoreLevelJson(vm.user.score);
vm.itemsPerPageConfig = MeanTorrentConfig.meanTorrentConfig.itemsPerPage;
vm.announceConfig = MeanTorrentConfig.meanTorrentConfig.announce;
/**
* buildPager
*/
vm.buildPager = function () {
vm.pagedItems = [];
vm.itemsPerPage = vm.itemsPerPageConfig.followListPerPage;
vm.currentPage = 1;
vm.figureOutItemsToDisplay();
};
/**
* figureOutItemsToDisplay
* @param callback
*/
vm.figureOutItemsToDisplay = function (callback) {
vm.getUserFollowers(vm.currentPage, function (items) {
vm.filterLength = items.total;
vm.pagedItems = items;
if (callback) callback();
});
};
/**
* getUserFollowers
*/
vm.getUserFollowers = function (p, callback) {
vm.statusMsg = 'FOLLOW.STATUS_GETTING';
UsersService.getUserFollowers({
userId: vm.user._id,
skip: (p - 1) * vm.itemsPerPage,
limit: vm.itemsPerPage
})
.then(onSuccess)
.catch(onError);
function onSuccess(data) {
vm.statusMsg = undefined;
mtDebug.info(data);
callback(data);
}
function onError(data) {
vm.statusMsg = 'FOLLOW.STATUS_GETTING_ERROR';
}
};
/**
* pageChanged
*/
vm.pageChanged = function () {
var element = angular.element('#top_of_follow_list');
vm.figureOutItemsToDisplay(function () {
$timeout(function () {
$('html,body').animate({scrollTop: element[0].offsetTop - 60}, 200);
}, 10);
});
};
/**
* followTo
*/
vm.followTo = function (u) {
UsersService.userFollowTo({
userId: u._id
}).then(onSuccess)
.catch(onError);
function onSuccess(response) {
mtDebug.info(response);
vm.me = Authentication.user = response;
$rootScope.$broadcast('user-follow-changed', response);
NotifycationService.showSuccessNotify('FOLLOW_SUCCESSFULLY');
}
function onError(response) {
NotifycationService.showErrorNotify($translate.instant(response.data.message, {name: u.displayName}), 'FOLLOW_ERROR');
}
};
/**
* unFollowTo
*/
vm.unFollowTo = function (u) {
UsersService.userUnfollowTo({
userId: u._id
}).then(onSuccess)
.catch(onError);
function onSuccess(response) {
mtDebug.info(response);
vm.me = Authentication.user = response;
$rootScope.$broadcast('user-follow-changed', response);
NotifycationService.showSuccessNotify('UNFOLLOW_SUCCESSFULLY');
}
function onError(response) {
NotifycationService.showErrorNotify(response.data.message, 'UNFOLLOW_ERROR');
}
};
/**
* inMyFollowing
* @returns {boolean}
*/
vm.inMyFollowing = function (u) {
return vm.me.following.indexOf(u._id) >= 0 ? true : false;
};
/**
* isContextUserSelf
* @returns {boolean}
*/
vm.isContextUserSelf = function (u) {
return vm.me.username === u.username;
}
}
}());

View File

@@ -0,0 +1,142 @@
(function () {
'use strict';
angular
.module('users')
.controller('UserFollowingController', UserFollowingController);
UserFollowingController.$inject = ['$scope', '$rootScope', '$state', '$timeout', '$translate', 'Authentication', 'UsersService', 'ScoreLevelService', 'MeanTorrentConfig', 'DebugConsoleService',
'NotifycationService', 'userResolve'];
function UserFollowingController($scope, $rootScope, $state, $timeout, $translate, Authentication, UsersService, ScoreLevelService, MeanTorrentConfig, mtDebug,
NotifycationService, userResolve) {
var vm = this;
vm.me = Authentication.user;
vm.user = userResolve;
vm.scoreLevelData = ScoreLevelService.getScoreLevelJson(vm.user.score);
vm.itemsPerPageConfig = MeanTorrentConfig.meanTorrentConfig.itemsPerPage;
vm.announceConfig = MeanTorrentConfig.meanTorrentConfig.announce;
/**
* buildPager
*/
vm.buildPager = function () {
vm.pagedItems = [];
vm.itemsPerPage = vm.itemsPerPageConfig.followListPerPage;
vm.currentPage = 1;
vm.figureOutItemsToDisplay();
};
/**
* figureOutItemsToDisplay
* @param callback
*/
vm.figureOutItemsToDisplay = function (callback) {
vm.getUserFollowing(vm.currentPage, function (items) {
vm.filterLength = items.total;
vm.pagedItems = items;
if (callback) callback();
});
};
/**
* getUserFollowing
*/
vm.getUserFollowing = function (p, callback) {
vm.statusMsg = 'FOLLOW.STATUS_GETTING';
UsersService.getUserFollowing({
userId: vm.user._id,
skip: (p - 1) * vm.itemsPerPage,
limit: vm.itemsPerPage
})
.then(onSuccess)
.catch(onError);
function onSuccess(data) {
vm.statusMsg = undefined;
mtDebug.info(data);
callback(data);
}
function onError(data) {
vm.statusMsg = 'FOLLOW.STATUS_GETTING_ERROR';
}
};
/**
* pageChanged
*/
vm.pageChanged = function () {
var element = angular.element('#top_of_follow_list');
vm.figureOutItemsToDisplay(function () {
$timeout(function () {
$('html,body').animate({scrollTop: element[0].offsetTop - 60}, 200);
}, 10);
});
};
/**
* followTo
*/
vm.followTo = function (u) {
UsersService.userFollowTo({
userId: u._id
}).then(onSuccess)
.catch(onError);
function onSuccess(response) {
mtDebug.info(response);
vm.me = Authentication.user = response;
$rootScope.$broadcast('user-follow-changed', response);
NotifycationService.showSuccessNotify('FOLLOW_SUCCESSFULLY');
}
function onError(response) {
NotifycationService.showErrorNotify($translate.instant(response.data.message, {name: u.displayName}), 'FOLLOW_ERROR');
}
};
/**
* unFollowTo
*/
vm.unFollowTo = function (u) {
UsersService.userUnfollowTo({
userId: u._id
}).then(onSuccess)
.catch(onError);
function onSuccess(response) {
mtDebug.info(response);
vm.me = Authentication.user = response;
$rootScope.$broadcast('user-follow-changed', response);
NotifycationService.showSuccessNotify('UNFOLLOW_SUCCESSFULLY');
}
function onError(response) {
NotifycationService.showErrorNotify(response.data.message, 'UNFOLLOW_ERROR');
}
};
/**
* inMyFollowing
* @returns {boolean}
*/
vm.inMyFollowing = function (u) {
return vm.me.following.indexOf(u._id) >= 0 ? true : false;
};
/**
* isContextUserSelf
* @returns {boolean}
*/
vm.isContextUserSelf = function (u) {
return vm.me.username === u.username;
}
}
}());

View File

@@ -79,6 +79,16 @@
method: 'GET',
url: '/api/users/following',
isArray: true
},
userFollowers: {
method: 'GET',
url: '/api/users/:userId/followers',
isArray: true
},
userFollowing: {
method: 'GET',
url: '/api/users/:userId/following',
isArray: true
}
});
@@ -128,6 +138,12 @@
},
getMyFollowing: function (params) {
return this.myFollowing(params).$promise;
},
getUserFollowers: function (params) {
return this.userFollowers(params).$promise;
},
getUserFollowing: function (params) {
return this.userFollowing(params).$promise;
}
});

View File

@@ -2,7 +2,7 @@
<div class="page-header">
<div class="row">
<div class="col-md-5 col-md-offset-1">
<h3 ng-bind="vm.user.displayName"></h3>
<h3 class="userinfo-name" ng-bind="vm.user.displayName"></h3>
</div>
<div class="col-md-5">
<a class="btn btn-primary pull-right margin-left-10" ng-if="vm.authentication.user.isAdmin"
@@ -173,10 +173,14 @@
<li class="status-divider"></li>
<dt class="h-line">{{ 'STATUS_FIELD.FOLLOWERS' | translate}}</dt>
<dd class="h-line">{{ vm.user.followers.length }}</dd>
<dd class="h-line">{{ vm.user.followers.length }}
<a class="pull-right" ui-sref="follow.userFollowers({userId: vm.user._id})" ng-if="vm.user.followers.length>0">{{ 'STATUS_FIELD.DETAIL' | translate }}</a>
</dd>
<dt class="h-line">{{ 'STATUS_FIELD.FOLLOWING' | translate}}</dt>
<dd class="h-line">{{ vm.user.following.length }}</dd>
<dd class="h-line">{{ vm.user.following.length }}
<a class="pull-right" ui-sref="follow.userFollowing({userId: vm.user._id})" ng-if="vm.user.following.length>0">{{ 'STATUS_FIELD.DETAIL' | translate }}</a>
</dd>
<li class="status-divider"></li>

View File

@@ -0,0 +1,72 @@
<div ng-init="vm.buildPager();">
<div class="container">
<div class="margin-top-30">
<h4 >
{{vm.user.displayName}} - {{'PAGETITLE.USER_FOLLOWERS' | translate}} <span class="badge badge_default">{{vm.user.followers.length}}</span>
</h4>
</div>
<div class="margin-bottom-30 margin-top-10">
<div class="jumbotron text-center" ng-if="vm.statusMsg || vm.pagedItems.length<=0">
<div ng-if="vm.statusMsg">
<h3 class="text-muted">{{vm.statusMsg | translate}}</h3>
</div>
<div ng-if="!vm.statusMsg && vm.pagedItems.length<=0">
<i class="fa fa-users fa-2x text-muted" aria-hidden="true"></i>
<h3 class="text-muted">{{'FOLLOW.NO_FOLLOWERS' | translate}}</h3>
</div>
</div>
<div ng-if="vm.pagedItems.length>0">
<div id="top_of_follow_list">
<div class="pagination-border-bottom">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
<div class="follow-list row">
<div ng-repeat="item in vm.pagedItems">
<div data-ng-if="$index != 0 && $index % 2 == 0" class="clearfix visible-sm-block "></div>
<div data-ng-if="$index != 0 && $index % 3 == 0" class="clearfix visible-md-block visible-lg-block"></div>
<div class="col-sm-6 col-md-4">
<div class="follow-item" mt-scale-by-mouse="{scale: 1.1, duration: '.5s'}">
<span user-info="item" info-class="follow-avatar" info-avatar></span>
<p><span user-info="item" info-class="follow-name" info-name></span><span message-to="item"
to-class="message-to-icon"></span></p>
<span vip-flag="item" vip-class="follow-vip"></span>
<span score-level-curr="item" class="follow-level"></span>
<span class="user-ud-text" ng-if="vm.announceConfig.privateTorrentCmsMode">
<span up-down-flag="item" up-down-class="follow-up-down"></span>
</span>
<a class="btn btn-default btn-sm btn-bold btn-width-80 btn-unfollow"
ng-click="vm.followTo(item)"
ng-if="!vm.isContextUserSelf(item) && !vm.inMyFollowing(item)">
{{ 'STATUS_FIELD.BTN_FOLLOW' | translate}}
</a>
<a class="btn btn-default btn-sm btn-bold btn-width-80 btn-unfollow"
ng-click="vm.unFollowTo(item)"
ng-if="!vm.isContextUserSelf(item) && vm.inMyFollowing(item)">
{{ 'STATUS_FIELD.BTN_UNFOLLOW' | translate}}
</a>
</div>
</div>
</div>
</div>
<div class="pagination-border-top">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,72 @@
<div ng-init="vm.buildPager();">
<div class="container">
<div class="margin-top-30">
<h4 >
{{vm.user.displayName}} - {{'PAGETITLE.USER_FOLLOWING' | translate}} <span class="badge badge_default">{{vm.user.following.length}}</span>
</h4>
</div>
<div class="margin-bottom-30 margin-top-10">
<div class="jumbotron text-center" ng-if="vm.statusMsg || vm.pagedItems.length<=0">
<div ng-if="vm.statusMsg">
<h3 class="text-muted">{{vm.statusMsg | translate}}</h3>
</div>
<div ng-if="!vm.statusMsg && vm.pagedItems.length<=0">
<i class="fa fa-users fa-2x text-muted" aria-hidden="true"></i>
<h3 class="text-muted">{{'FOLLOW.NO_FOLLOWERS' | translate}}</h3>
</div>
</div>
<div ng-if="vm.pagedItems.length>0">
<div id="top_of_follow_list">
<div class="pagination-border-bottom">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
<div class="follow-list row">
<div ng-repeat="item in vm.pagedItems">
<div data-ng-if="$index != 0 && $index % 2 == 0" class="clearfix visible-sm-block"></div>
<div data-ng-if="$index != 0 && $index % 3 == 0" class="clearfix visible-md-block visible-lg-block"></div>
<div class="col-sm-6 col-md-4">
<div class="follow-item" mt-scale-by-mouse="{scale: 1.1, duration: '.5s'}">
<span user-info="item" info-class="follow-avatar" info-avatar></span>
<p><span user-info="item" info-class="follow-name" info-name></span><span message-to="item"
to-class="message-to-icon"></span></p>
<span vip-flag="item" vip-class="follow-vip"></span>
<span score-level-curr="item" class="follow-level"></span>
<span class="user-ud-text" ng-if="vm.announceConfig.privateTorrentCmsMode">
<span up-down-flag="item" up-down-class="follow-up-down"></span>
</span>
<a class="btn btn-default btn-sm btn-bold btn-width-80 btn-unfollow"
ng-click="vm.followTo(item)"
ng-if="!vm.isContextUserSelf(item) && !vm.inMyFollowing(item)">
{{ 'STATUS_FIELD.BTN_FOLLOW' | translate}}
</a>
<a class="btn btn-default btn-sm btn-bold btn-width-80 btn-unfollow"
ng-click="vm.unFollowTo(item)"
ng-if="!vm.isContextUserSelf(item) && vm.inMyFollowing(item)">
{{ 'STATUS_FIELD.BTN_UNFOLLOW' | translate}}
</a>
</div>
</div>
</div>
</div>
<div class="pagination-border-top">
<ul uib-pagination boundary-links="true" max-size="8" items-per-page="vm.itemsPerPage" total-items="vm.filterLength"
ng-model="vm.currentPage"
ng-change="vm.pageChanged()"
first-text="{{ 'PAGE_TEXT_FIRST' | translate}}" previous-text="{{ 'PAGE_TEXT_PREVIOUS' | translate}}"
next-text="{{ 'PAGE_TEXT_NEXT' | translate}}" last-text="{{ 'PAGE_TEXT_LAST' | translate}}">
</ul>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -115,3 +115,79 @@ exports.myFollowing = function (req, res) {
}
});
};
/**
* getUserFollowers
* @param req
* @param res
*/
exports.getUserFollowers = function (req, res) {
var skip = 0;
var limit = 0;
var user = req.model;
var me = req.user;
if (req.query.skip !== undefined) {
skip = parseInt(req.query.skip, 10);
}
if (req.query.limit !== undefined) {
limit = parseInt(req.query.limit, 10);
}
if (me.followers.indexOf(user._id) >= 0 || me.following.indexOf(user._id) >= 0 || me.isOper) {
User.populate(user, {
path: 'followers',
select: 'username displayName profileImageURL uploaded downloaded isVip score'
}, function (err, u) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(u.followers ? u.followers.slice(skip, skip + limit) : []);
}
});
} else {
return res.status(403).json({
message: 'SERVER.USER_IS_NOT_AUTHORIZED'
});
}
};
/**
* getUserFollowing
* @param req
* @param res
*/
exports.getUserFollowing = function (req, res) {
var skip = 0;
var limit = 0;
var user = req.model;
var me = req.user;
if (req.query.skip !== undefined) {
skip = parseInt(req.query.skip, 10);
}
if (req.query.limit !== undefined) {
limit = parseInt(req.query.limit, 10);
}
if (me.followers.indexOf(user._id) >= 0 || me.following.indexOf(user._id) >= 0 || me.isOper) {
User.populate(user, {
path: 'following',
select: 'username displayName profileImageURL uploaded downloaded isVip score'
}, function (err, u) {
if (err) {
return res.status(422).send({
message: errorHandler.getErrorMessage(err)
});
} else {
res.json(u.following ? u.following.slice(skip, skip + limit) : []);
}
});
} else {
return res.status(403).json({
message: 'SERVER.USER_IS_NOT_AUTHORIZED'
});
}
};

View File

@@ -36,6 +36,8 @@ exports.invokeRolesPolicies = function () {
{resources: '/api/users/:userId/leeching', permissions: '*'},
{resources: '/api/users/:userId/warning', permissions: '*'},
{resources: '/api/users/:userId/uptotal', permissions: '*'},
{resources: '/api/users/:userId/followers', permissions: '*'},
{resources: '/api/users/:userId/following', permissions: '*'},
{resources: '/api/users/:userId/resetImage', permissions: '*'}
]
},
@@ -43,7 +45,9 @@ exports.invokeRolesPolicies = function () {
roles: ['user'],
allows: [
{resources: '/api/users/:userId', permissions: ['get']},
{resources: '/api/users/:userId/uptotal', permissions: ['get']}
{resources: '/api/users/:userId/uptotal', permissions: ['get']},
{resources: '/api/users/:userId/followers', permissions: ['get']},
{resources: '/api/users/:userId/following', permissions: ['get']}
]
}
]

View File

@@ -4,7 +4,8 @@
* Module dependencies
*/
var adminPolicy = require('../policies/admin.server.policy'),
admin = require('../controllers/admin.server.controller');
admin = require('../controllers/admin.server.controller'),
follow = require('../controllers/users/users.follow.server.controller');
module.exports = function (app) {
// User route registration first. Ref: #713
@@ -43,6 +44,10 @@ module.exports = function (app) {
.get(admin.getUserWarning);
app.route('/api/users/:userId/uptotal').all(adminPolicy.isAllowed)
.get(admin.getUserUploadedTotal);
app.route('/api/users/:userId/followers').all(adminPolicy.isAllowed)
.get(follow.getUserFollowers);
app.route('/api/users/:userId/following').all(adminPolicy.isAllowed)
.get(follow.getUserFollowing);
app.route('/api/users/:userId/resetImage').all(adminPolicy.isAllowed)
.put(admin.resetUserProfileImage);