added user query by value to graph

This commit is contained in:
Wyatt Johnson
2018-01-03 14:01:51 -07:00
parent 0572a1c97d
commit 797cea4f63
8 changed files with 52 additions and 146 deletions
+7
View File
@@ -27,7 +27,14 @@ const CONFIG = {
// WEBPACK indicates when webpack is currently building.
WEBPACK: process.env.WEBPACK === 'TRUE',
// APOLLO_ENGINE_KEY specifies the key used to connect Talk to
// https://engine.apollo.com/ for tracing of GraphQL requests.
//
// Note: Apollo Engine is a premium service, may not be free for certain
// volumes of queries.
APOLLO_ENGINE_KEY: process.env.APOLLO_ENGINE_KEY || null,
// ENABLE_TRACING is true when the APOLLO_ENGINE_KEY is provided.
ENABLE_TRACING: Boolean(process.env.APOLLO_ENGINE_KEY),
// EMAIL_SUBJECT_PREFIX is the string before emails in the subject.
+38 -10
View File
@@ -8,6 +8,7 @@ const {
} = require('../../perms/constants');
const UsersService = require('../../services/users');
const {escapeRegExp} = require('../../services/regex');
const UserModel = require('../../models/user');
const mergeState = (query, state) => {
@@ -72,14 +73,48 @@ const genUserByIDs = async (context, ids) => {
* @param {Object} context graph context
* @param {Object} query query terms to apply to the users query
*/
const getUsersByQuery = async ({user}, {ids, limit, cursor, state, action_type, sortOrder}) => {
const getUsersByQuery = async ({user}, {limit, cursor, value = '', state, action_type, sortOrder}) => {
let query = UserModel.find();
if (action_type || state) {
if (action_type || state || value.length > 0) {
if (!user || !user.can(SEARCH_OTHER_USERS)) {
return null;
}
if (value.length > 0) {
// Lowercase the search term and escape any regex characters.
value = escapeRegExp(value).toLowerCase();
// Compile the prefix search regex.
const $regex = new RegExp(`^${value}`);
// Merge in the regex params.
query.merge({
$or: [
// Search by a prefix match on the username.
{
lowercaseUsername: {
$regex,
},
},
// Search by a prefix match on the email address.
{
profiles: {
$elemMatch: {
id: {
$regex,
},
provider: 'local',
},
},
},
],
});
}
if (state) {
mergeState(query, state);
}
@@ -93,14 +128,6 @@ const getUsersByQuery = async ({user}, {ids, limit, cursor, state, action_type,
}
}
if (ids) {
query = query.find({
id: {
$in: ids
}
});
}
if (cursor) {
if (sortOrder === 'DESC') {
query = query.where({
@@ -125,6 +152,7 @@ const getUsersByQuery = async ({user}, {ids, limit, cursor, state, action_type,
// Sort by created_at.
query.sort({created_at: sortOrder === 'DESC' ? -1 : 1});
// Execute the query.
const nodes = await query.exec();
// The hasNextPage is always handled the same (ask for one more than we need,
+1 -1
View File
@@ -78,7 +78,7 @@ const RootQuery = {
// This endpoint is used for loading the user moderation queues (users whose username has been flagged),
// so hide it in the event that we aren't an admin.
async users(_, {query}, {user, loaders: {Users}}) {
users(_, {query}, {user, loaders: {Users}}) {
if (user == null || !user.can(SEARCH_OTHER_USERS)) {
return null;
}
+2 -3
View File
@@ -50,7 +50,7 @@ const User = {
return tokens;
},
async ignoredUsers({id}, args, {user, loaders: {Users}}) {
ignoredUsers({id}, args, {user, loaders: {Users}}) {
// Only allow a logged in user that is either the current user or is a staff
// member to access the ignoredUsers of a given user.
@@ -63,8 +63,7 @@ const User = {
return [];
}
const connection = await Users.getByQuery({ids: user.ignoresUsers});
return connection.nodes;
return Users.getByID.loadMany(user.ignoresUsers);
},
role({id, role}, _, {user}) {
+4
View File
@@ -233,8 +233,12 @@ input UsersQuery {
# Users returned will only be ones which have at least one action of this.
action_type: ACTION_TYPE
# state will filter the users to a specific set of users that meet.
state: UserStateInput
# value is the search string to use to search for a pa
value: String = ""
# Limit the number of results to be returned.
limit: Int = 10
-50
View File
@@ -5,56 +5,6 @@ const errors = require('../../../errors');
const authorization = require('../../../middleware/authorization');
const Limit = require('../../../services/limit');
router.get('/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => {
const {
value = '',
field = 'created_at',
page = 1,
asc = 'false',
limit = 20 // Total Per Page
} = req.query;
try {
const queryOpts = {
sort: {[field]: (asc === 'true') ? 1 : -1},
skip: (page - 1) * limit,
limit
};
let [result, count] = await Promise.all([
UsersService
.search(value)
.sort(queryOpts.sort)
.skip(parseInt(queryOpts.skip))
.limit(parseInt(queryOpts.limit))
.lean(),
UsersService.search(value).count()
]);
res.json({
result,
limit: Number(limit),
count,
page: Number(page),
totalPages: Math.ceil(count / (limit === 0 ? 1 : limit))
});
} catch (e) {
next(e);
}
});
router.post('/:user_id/role', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => {
try {
await UsersService.setRole(req.params.user_id, req.body.role);
res.status(204).end();
} catch (e) {
next(e);
}
});
// create a local user.
router.post('/', async (req, res, next) => {
const {email, password, username} = req.body;
-42
View File
@@ -28,7 +28,6 @@ const MailerService = require('./mailer');
const i18n = require('./i18n');
const Wordlist = require('./wordlist');
const DomainList = require('./domain_list');
const {escapeRegExp} = require('./regex');
const EMAIL_CONFIRM_JWT_SUBJECT = 'email_confirm';
const PASSWORD_RESET_JWT_SUBJECT = 'password_reset';
@@ -759,47 +758,6 @@ class UsersService {
return [user, loc];
}
/**
* Finds a user using a value which gets compared using a prefix match against
* the user's email address and/or their username.
* @param {String} value value to search by
* @return {Promise}
*/
static search(value) {
if (!value || typeof value !== 'string' || value.length === 0) {
return UserModel.find({});
}
value = escapeRegExp(value).toLowerCase();
// Compile the prefix search regex.
const $regex = new RegExp(`^${value}`);
return UserModel.find({
$or: [
// Search by a prefix match on the username.
{
lowercaseUsername: {
$regex,
},
},
// Search by a prefix match on the email address.
{
profiles: {
$elemMatch: {
id: {
$regex,
},
provider: 'local',
},
},
},
],
});
}
/**
* Returns a count of the current users.
* @return {Promise}
-40
View File
@@ -179,46 +179,6 @@ describe('services.UsersService', () => {
});
});
describe('#search', () => {
it('should return all the results without a value', async () => {
expect(await UsersService.search()).to.have.length(3);
});
it('should match the search terms', async () => {
const tests = [
{
search: 'Stamp',
results: 1,
id: mockUsers[0].id,
},
{
search: 'sockmonster',
results: 1,
id: mockUsers[1].id,
},
{
search: 'marvel',
results: 1,
id: mockUsers[2].id,
},
{
search: 'marvel',
results: 1,
id: mockUsers[2].id,
},
];
for (const test of tests) {
const users = await UsersService.search(test.search);
expect(users).to.have.length(test.results);
if (test.results === 1) {
expect(users[0]).to.have.property('id', test.id);
}
}
});
});
[
{func: 'changeUsername', okStatus: 'REJECTED', notOKStatus: 'UNSET', newStatus: 'CHANGED'},
{func: 'setUsername', okStatus: 'UNSET', notOKStatus: 'REJECTED', newStatus: 'SET'},