diff --git a/autocannon.js b/autocannon.js new file mode 100644 index 0000000000..383047b4f7 --- /dev/null +++ b/autocannon.js @@ -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); diff --git a/website/client/src/components/admin-panel/index.vue b/website/client/src/components/admin-panel/index.vue index acd58c31d2..5cd6e89826 100644 --- a/website/client/src/components/admin-panel/index.vue +++ b/website/client/src/components/admin-panel/index.vue @@ -2,29 +2,31 @@

Admin Panel

- -
-
+ +
- + +
+ type="button" + @click="searchUsers(userIdentifier)">Search +
+
+ -
- -
+
@@ -33,6 +35,10 @@ .uidField { min-width: 45ch; } + + .input-group-append { + width:auto; + } diff --git a/website/client/src/router/index.js b/website/client/src/router/index.js index 5e51cff9fc..55bc30b2d8 100644 --- a/website/client/src/router/index.js +++ b/website/client/src/router/index.js @@ -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: [ diff --git a/website/client/src/store/actions/adminPanel.js b/website/client/src/store/actions/adminPanel.js new file mode 100644 index 0000000000..43e1805429 --- /dev/null +++ b/website/client/src/store/actions/adminPanel.js @@ -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; +} diff --git a/website/client/src/store/actions/index.js b/website/client/src/store/actions/index.js index f514eec99a..d9be79396d 100644 --- a/website/client/src/store/actions/index.js +++ b/website/client/src/store/actions/index.js @@ -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, diff --git a/website/server/controllers/api-v4/admin.js b/website/server/controllers/api-v4/admin.js new file mode 100644 index 0000000000..acb6fe4ea2 --- /dev/null +++ b/website/server/controllers/api-v4/admin.js @@ -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;