Add option to search for users by email or username in admin panel
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
// eslint-disable-next-line import/no-commonjs, import/no-unresolved
|
||||
const autocannon = require('autocannon');
|
||||
|
||||
autocannon({
|
||||
url: 'http://localhost:3000/api/v4/inbox/messages',
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'x-api-user': 'd7ee6e45-7db1-44fd-8c1f-195ef6cd52d6',
|
||||
'x-api-key': '8a8f9790-a27b-4dff-b403-bddc5cf8fe53',
|
||||
},
|
||||
connections: 100, // default
|
||||
pipelining: 1, // default
|
||||
duration: 10, // default
|
||||
workers: 4,
|
||||
}, console.log);
|
||||
@@ -2,29 +2,31 @@
|
||||
<div class="row standard-page">
|
||||
<div class="well col-12">
|
||||
<h1>Admin Panel</h1>
|
||||
|
||||
<div>
|
||||
<form
|
||||
class="form-inline"
|
||||
@submit.prevent="loadHero(userIdentifier)"
|
||||
>
|
||||
<form
|
||||
class="form-inline"
|
||||
@submit.prevent="loadUser(userIdentifier)"
|
||||
>
|
||||
<div class="input-group">
|
||||
<input
|
||||
v-model="userIdentifier"
|
||||
class="form-control uidField"
|
||||
type="text"
|
||||
:placeholder="'User ID or Username; blank for your account'"
|
||||
:placeholder="'User-ID, Username or E-Mail; blank for your account'"
|
||||
>
|
||||
<input
|
||||
type="submit"
|
||||
value="Load User"
|
||||
<div class="input-group-append">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="button"
|
||||
@click="loadUser(userIdentifier)">Load User</button>
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
type="button"
|
||||
@click="searchUsers(userIdentifier)">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div>
|
||||
<router-view @changeUserIdentifier="changeUserIdentifier" />
|
||||
</div>
|
||||
<router-view @changeUserIdentifier="changeUserIdentifier" class="mt-3" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -33,6 +35,10 @@
|
||||
.uidField {
|
||||
min-width: 45ch;
|
||||
}
|
||||
|
||||
.input-group-append {
|
||||
width:auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -62,7 +68,20 @@ export default {
|
||||
// (useful if we want to re-fetch the user after making changes).
|
||||
this.userIdentifier = newId;
|
||||
},
|
||||
async loadHero (userIdentifier) {
|
||||
async searchUsers (userIdentifier) {
|
||||
this.$router.push({
|
||||
name: 'adminPanelSearch',
|
||||
params: { userIdentifier },
|
||||
}).catch(failure => {
|
||||
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||
// the admin has requested that the same user be displayed again so reload the page
|
||||
// (e.g., if they changed their mind about changes they were making)
|
||||
this.$router.go();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
async loadUser (userIdentifier) {
|
||||
const id = userIdentifier || this.user._id;
|
||||
|
||||
this.$router.push({
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="alert alert-warning" role="alert"
|
||||
v-if="noUsersFound"
|
||||
>
|
||||
Could not find any matching users.
|
||||
</div>
|
||||
<div class="list-group col-4"
|
||||
v-if="users.length > 0">
|
||||
<a href="#" class="list-group-item list-group-item-action"
|
||||
v-for="user in users"
|
||||
:key="user._id"
|
||||
@click="loadUser(user._id)"
|
||||
>
|
||||
<div class="d-flex w-100 justify-content-between">
|
||||
<h5 class="mb-1">{{ user.profile.name }}</h5>
|
||||
<small>{{ user._id }}</small>
|
||||
</div>
|
||||
<p class="mb-1"
|
||||
:class="{'font-weight-bold': matchValueToIdentifier(user.auth.local.username)}">
|
||||
@{{ user.auth.local.username }}</p>
|
||||
<p class="mb-0">
|
||||
<span
|
||||
v-for="email in userEmails(user)"
|
||||
:class="{'font-weigh-bold': matchValueToIdentifier(email)}"
|
||||
:key="email">
|
||||
{{ email }}
|
||||
</span>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueRouter from 'vue-router';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
userIdentifier: '',
|
||||
users: [],
|
||||
noUsersFound: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({ user: 'user.data' }),
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.userIdentifier = to.params.userIdentifier;
|
||||
next();
|
||||
},
|
||||
watch: {
|
||||
userIdentifier () {
|
||||
this.$store.dispatch('adminPanel:searchUsers', { userIdentifier: this.userIdentifier }).then(users => {
|
||||
this.users = users;
|
||||
this.noUsersFound = users.length === 0;
|
||||
});
|
||||
this.$emit('changeUserIdentifier', this.userIdentifier); // change user identifier in Admin Panel's form
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.userIdentifier = this.$route.params.userIdentifier;
|
||||
},
|
||||
methods: {
|
||||
matchValueToIdentifier (value) {
|
||||
return value.toLowerCase().includes(this.userIdentifier.toLowerCase());
|
||||
},
|
||||
userEmails (user) {
|
||||
const emails = [];
|
||||
if (user.auth.local.email) emails.push(user.auth.local.email);
|
||||
if (user.auth.google && user.auth.google.email) {
|
||||
const email = user.auth.google.email;
|
||||
if (typeof email === 'string') emails.push(email);
|
||||
else if (Array.isArray(email)) emails.push(...email);
|
||||
}
|
||||
if (user.auth.apple && user.auth.apple.email) {
|
||||
const email = user.auth.apple.email;
|
||||
if (typeof email === 'string') emails.push(email);
|
||||
else if (Array.isArray(email)) emails.push(...email);
|
||||
}
|
||||
if (user.auth.facebook && user.auth.facebook.email) {
|
||||
const email = user.auth.facebook.email;
|
||||
if (typeof email === 'string') emails.push(email);
|
||||
else if (Array.isArray(email)) emails.push(...email);
|
||||
}
|
||||
return emails;
|
||||
},
|
||||
async loadUser (userIdentifier) {
|
||||
const id = userIdentifier || this.user._id;
|
||||
|
||||
this.$router.push({
|
||||
name: 'adminPanelUser',
|
||||
params: { userIdentifier: id },
|
||||
}).catch(failure => {
|
||||
if (isNavigationFailure(failure, NavigationFailureType.duplicated)) {
|
||||
// the admin has requested that the same user be displayed again so reload the page
|
||||
// (e.g., if they changed their mind about changes they were making)
|
||||
this.$router.go();
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -22,6 +22,7 @@ const HeroesPage = () => import(/* webpackChunkName: "hall" */'@/components/hall
|
||||
// Admin Panel
|
||||
const AdminPanelPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin-panel');
|
||||
const AdminPanelUserPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin-panel/user-support');
|
||||
const AdminPanelSearchPage = () => import(/* webpackChunkName: "admin-panel" */'@/components/admin-panel/search');
|
||||
|
||||
// Except for tasks that are always loaded all the other main level
|
||||
// All the main level
|
||||
@@ -193,9 +194,19 @@ const router = new VueRouter({
|
||||
],
|
||||
},
|
||||
children: [
|
||||
{
|
||||
name: 'adminPanelSearch',
|
||||
path: 'search/:userIdentifier',
|
||||
component: AdminPanelSearchPage,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
'userSupport',
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'adminPanelUser',
|
||||
path: ':userIdentifier', // User ID or Username
|
||||
path: ':userIdentifier',
|
||||
component: AdminPanelUserPage,
|
||||
meta: {
|
||||
privilegeNeeded: [
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import axios from 'axios';
|
||||
|
||||
export async function searchUsers (store, payload) {
|
||||
const url = `/api/v4/admin/search/${payload.userIdentifier}`;
|
||||
const response = await axios.get(url);
|
||||
return response.data.data;
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { flattenAndNamespace } from '@/libs/store/helpers/internals';
|
||||
|
||||
import * as adminPanel from './adminPanel';
|
||||
import * as common from './common';
|
||||
import * as user from './user';
|
||||
import * as tasks from './tasks';
|
||||
@@ -24,6 +25,7 @@ import * as faq from './faq';
|
||||
// Example: fetch in user.js -> 'user:fetch'
|
||||
|
||||
const actions = flattenAndNamespace({
|
||||
adminPanel,
|
||||
common,
|
||||
user,
|
||||
tasks,
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import validator from 'validator';
|
||||
import { authWithHeaders } from '../../middlewares/auth';
|
||||
import { ensurePermission } from '../../middlewares/ensureAccessRight';
|
||||
import { model as User } from '../../models/user';
|
||||
|
||||
const api = {};
|
||||
|
||||
/**
|
||||
* @api {get} /api/v4/admin/search/:userIdentifier Search for users by username or email
|
||||
* @apiParam (Path) {String} userIdentifier The username or email of the user to search for
|
||||
* @apiName SearchUsers
|
||||
* @apiGroup Admin
|
||||
* @apiPermission Admin
|
||||
*
|
||||
* @apiDescription Returns a list of users that match the search criteria
|
||||
*
|
||||
* @apiSuccess {Object} data The User list
|
||||
*
|
||||
* @apiUse NoAuthHeaders
|
||||
* @apiUse NoAccount
|
||||
* @apiUse NoUser
|
||||
* @apiUse NotAdmin
|
||||
*/
|
||||
api.getHero = {
|
||||
method: 'GET',
|
||||
url: '/admin/search/:userIdentifier',
|
||||
middlewares: [authWithHeaders(), ensurePermission('userSupport')],
|
||||
async handler (req, res) {
|
||||
req.checkParams('userIdentifier', res.t('userIdentifierRequired')).notEmpty();
|
||||
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
const { userIdentifier } = req.params;
|
||||
|
||||
const re = new RegExp(String.raw`${userIdentifier}`);
|
||||
|
||||
let query;
|
||||
if (validator.isUUID(userIdentifier)) {
|
||||
query = { _id: userIdentifier };
|
||||
} else if (validator.isEmail(userIdentifier)) {
|
||||
query = {
|
||||
$or: [
|
||||
{ 'auth.local.email': { $regex: re, $options: 'i' } },
|
||||
{ 'auth.google.email': { $regex: re, $options: 'i' } },
|
||||
{ 'auth.apple.email': { $regex: re, $options: 'i' } },
|
||||
{ 'auth.facebook.email': { $regex: re, $options: 'i' } },
|
||||
],
|
||||
};
|
||||
} else {
|
||||
query = { 'auth.local.lowerCaseUsername': { $regex: re, $options: 'i' } };
|
||||
}
|
||||
|
||||
console.log(query);
|
||||
|
||||
const users = await User
|
||||
.find(query)
|
||||
.select('contributor backer profile auth')
|
||||
.limit(30)
|
||||
.lean()
|
||||
.exec();
|
||||
res.respond(200, users);
|
||||
},
|
||||
};
|
||||
|
||||
export default api;
|
||||
Reference in New Issue
Block a user