diff --git a/src/client/app/common/views/components/autocomplete.vue b/src/client/app/common/views/components/autocomplete.vue index cd6066877..b274eaa0a 100644 --- a/src/client/app/common/views/components/autocomplete.vue +++ b/src/client/app/common/views/components/autocomplete.vue @@ -132,7 +132,7 @@ export default Vue.extend({ this.users = users; this.fetching = false; } else { - (this as any).api('users/search_by_username', { + (this as any).api('users/search', { query: this.q, limit: 30 }).then(users => { diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts index d443d35b4..eda3f9572 100644 --- a/src/server/api/endpoints/users/search.ts +++ b/src/server/api/endpoints/users/search.ts @@ -1,33 +1,156 @@ import $ from 'cafy'; -import User, { pack, ILocalUser } from '../../../../models/user'; const escapeRegexp = require('escape-regexp'); +import User, { pack, ILocalUser, validateUsername, IUser } from '../../../../models/user'; +import getParams from '../../get-params'; + +export const meta = { + desc: { + ja: 'ユーザーを検索します。' + }, + + requireCredential: false, + + params: { + query: $.str.note({ + desc: { + ja: 'クエリ' + } + }), + + offset: $.num.optional.min(0).note({ + default: 0, + desc: { + ja: 'オフセット' + } + }), + + limit: $.num.optional.range(1, 100).note({ + default: 10, + desc: { + ja: '取得する数' + } + }), + + localOnly: $.bool.optional.note({ + default: false, + desc: { + ja: 'ローカルユーザーのみ検索対象にするか否か' + } + }), + }, +}; /** * Search a user */ export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'query' parameter - const [query, queryError] = $.str.pipe(x => x != '').get(params.query); - if (queryError) return rej('invalid query param'); + const [ps, psErr] = getParams(meta, params); + if (psErr) return rej(psErr); - // Get 'max' parameter - const [max = 10, maxErr] = $.num.optional.range(1, 30).get(params.max); - if (maxErr) return rej('invalid max param'); + const isUsername = validateUsername(ps.query.replace('@', '')); - const escapedQuery = escapeRegexp(query); + let users: IUser[] = []; - // Search users - const users = await User - .find({ - host: null, - $or: [{ - usernameLower: new RegExp(escapedQuery.replace('@', '').toLowerCase()) + if (isUsername) { + users = await User + .find({ + host: null, + usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())) }, { - name: new RegExp(escapedQuery) - }] - }, { - limit: max - }); + limit: ps.limit, + skip: ps.offset + }); + + if (users.length < ps.limit && !ps.localOnly) { + const otherUsers = await User + .find({ + host: { $ne: null }, + usernameLower: new RegExp('^' + escapeRegexp(ps.query.replace('@', '').toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < ps.limit) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: null, + usernameLower: new RegExp(escapeRegexp(ps.query.replace('@', '').toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < ps.limit && !ps.localOnly) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: { $ne: null }, + usernameLower: new RegExp(escapeRegexp(ps.query.replace('@', '').toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + } + + if (users.length < ps.limit) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: null, + name: new RegExp('^' + escapeRegexp(ps.query.toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < ps.limit && !ps.localOnly) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: { $ne: null }, + name: new RegExp('^' + escapeRegexp(ps.query.toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < ps.limit) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: null, + name: new RegExp(escapeRegexp(ps.query.toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } + + if (users.length < ps.limit && !ps.localOnly) { + const otherUsers = await User + .find({ + _id: { $nin: users.map(u => u._id) }, + host: { $ne: null }, + name: new RegExp(escapeRegexp(ps.query.toLowerCase())) + }, { + limit: ps.limit - users.length + }); + + users = users.concat(otherUsers); + } // Serialize res(await Promise.all(users.map(user => pack(user, me, { detail: true })))); diff --git a/src/server/api/endpoints/users/search_by_username.ts b/src/server/api/endpoints/users/search_by_username.ts deleted file mode 100644 index bfab37838..000000000 --- a/src/server/api/endpoints/users/search_by_username.ts +++ /dev/null @@ -1,70 +0,0 @@ -import $ from 'cafy'; -import User, { pack, ILocalUser } from '../../../../models/user'; -const escapeRegexp = require('escape-regexp'); - -/** - * Search a user by username - */ -export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { - // Get 'query' parameter - const [query, queryError] = $.str.get(params.query); - if (queryError) return rej('invalid query param'); - - // Get 'offset' parameter - const [offset = 0, offsetErr] = $.num.optional.min(0).get(params.offset); - if (offsetErr) return rej('invalid offset param'); - - // Get 'limit' parameter - const [limit = 10, limitErr] = $.num.optional.range(1, 100).get(params.limit); - if (limitErr) return rej('invalid limit param'); - - let users = await User - .find({ - host: null, - usernameLower: new RegExp('^' + escapeRegexp(query.toLowerCase())) - }, { - limit: limit, - skip: offset - }); - - if (users.length < limit) { - const otherUsers = await User - .find({ - host: { $ne: null }, - usernameLower: new RegExp('^' + escapeRegexp(query.toLowerCase())) - }, { - limit: limit - users.length - }); - - users = users.concat(otherUsers); - } - - if (users.length < limit) { - const otherUsers = await User - .find({ - _id: { $nin: users.map(u => u._id) }, - host: null, - usernameLower: new RegExp(escapeRegexp(query.toLowerCase())) - }, { - limit: limit - users.length - }); - - users = users.concat(otherUsers); - } - - if (users.length < limit) { - const otherUsers = await User - .find({ - _id: { $nin: users.map(u => u._id) }, - host: { $ne: null }, - usernameLower: new RegExp(escapeRegexp(query.toLowerCase())) - }, { - limit: limit - users.length - }); - - users = users.concat(otherUsers); - } - - // Serialize - res(await Promise.all(users.map(user => pack(user, me, { detail: true })))); -});