diff --git a/hydro/handler/home.js b/hydro/handler/home.js index 6035ca0f7..b376b4728 100644 --- a/hydro/handler/home.js +++ b/hydro/handler/home.js @@ -1,3 +1,4 @@ +const fs = require('fs'); const { VerifyPasswordError, UserAlreadyExistError, InvalidTokenError, NotFoundError, @@ -13,6 +14,7 @@ const message = require('../model/message'); const document = require('../model/document'); const system = require('../model/system'); const user = require('../model/user'); +const file = require('../model/file'); const setting = require('../model/setting'); const domain = require('../model/domain'); const discussion = require('../model/discussion'); @@ -140,13 +142,13 @@ class HomeSecurityHandler extends Handler { this.user.checkPassword(currentPassword); const udoc = await user.getByMail(mail); if (udoc) throw new UserAlreadyExistError(mail); - const [rid] = await token.add( + const [code] = await token.add( token.TYPE_CHANGEMAIL, await system.get('changemail_token_expire_seconds'), { uid: this.udoc._id, mail }, ); await mail.sendMail(mail, 'Change Email', 'user_changemail_mail.html', { - url: `/changeMail/${rid}`, uname: this.udoc.uname, + url: this.url('user_changemail_with_code', { code }), uname: this.udoc.uname, }); this.response.template = 'user_changemail_mail_sent.html'; } @@ -165,7 +167,7 @@ class HomeSecurityHandler extends Handler { async postDeleteAllTokens() { await token.delByUid(this.user._id); - this.response.redirect = '/login'; + this.response.redirect = this.url('user_login'); } } @@ -219,73 +221,47 @@ class UserChangemailWithCodeHandler extends Handler { user.setEmail(this.user._id, tdoc.mail), token.del(code, token.TYPE_CHANGEMAIL), ]); - this.response.redirect = '/home/security'; + this.response.redirect = this.url('home_security'); } } class HomeMessagesHandler extends Handler { - udoc(udict, key) { // eslint-disable-line class-methods-use-this - const udoc = udict[key]; - if (!udoc) return; - const gravatar_url = misc.gravatar(udoc.gravatar); - if (udoc.gravatar) udict[key] = { ...udoc, gravatar_url, gravatar: '' }; - } - - async prepare() { - this.checkPerm(PERM_LOGGEDIN); - } - async get() { // TODO(iceboy): projection, pagination. const messages = await message.getByUser(this.user._id); - const udict = await user.getList([ + const udict = await user.getList('system', [ ...messages.map((mdoc) => mdoc.from), ...messages.map((mdoc) => mdoc.to), ]); // TODO(twd2): improve here: const parsed = {}; for (const m of messages) { - if (m.from === this.user._id) { - if (!parsed[m.to]) { - parsed[m.to] = { - udoc: { ...udict[m.to], gravatar: misc.gravatar(udict[m.to].gravatar) }, - messages: [], - }; - } - parsed[m.to].messages.push(m); - } else { - if (!parsed[m.from]) { - parsed[m.from] = { - udoc: { ...udict[m.from], gravatar: misc.gravatar(udict[m.from].gravatar) }, - messages: [], - }; - } - parsed[m.from].messages.push(m); + const target = m.from === this.user._id ? m.to : m.from; + if (!parsed[target]) { + parsed[target] = { + _id: target, + udoc: { ...udict[target], gravatar: misc.gravatar(udict[target].gravatar) }, + messages: [], + }; } + parsed[target].messages.push(m); } const path = [ ['Hydro', 'homepage'], ['home_messages', null], ]; - this.response.body = { messages, udict, path }; + this.response.body = { messages: parsed, path }; this.response.template = 'home_messages.html'; } - async postSend({ uid, content, type = 'full' }) { + async postSend({ uid, content }) { const udoc = await user.getById('system', uid); - let mdoc = await message.send(this.user._id, uid, content); - if (type === 'single') { - mdoc = mdoc.reply[mdoc.reply.length - 1]; - } + const mdoc = await message.send(this.user._id, uid, content); // TODO(twd2): improve here: projection - mdoc.from_udoc = this.user; - mdoc.to_udoc = udoc; - this.udoc(mdoc, 'from'); - this.udoc(mdoc, 'to'); if (this.user._id !== uid) { - await bus.publish(`user_message-${uid}`, { type: 'new', data: mdoc }); + await bus.publish(`user_message-${uid}`, { mdoc, udoc }); } - this.back(type === 'full' ? { mdoc } : { reply: mdoc }); + this.back({ mdoc, udoc }); } async postDeleteMessage({ messageId }) { @@ -296,53 +272,43 @@ class HomeMessagesHandler extends Handler { class HomeMessagesConnectionHandler extends ConnectionHandler { async prepare() { - bus.subscribe([`message_received-${this.user._id}`], this.onMessageReceived); + bus.subscribe([`user_message-${this.user._id}`], (...args) => this.onMessageReceived(...args)); } async onMessageReceived(e) { - this.send(...e.value); + this.send(e.value); } - async clearup() { + async cleanup() { bus.unsubscribe(this.onMessageReceived); } } +// TODO draft +class HomeFileHandler extends Handler { + async get() { + const ufdocs = await file.getMulti({ owner: this.user._id }).toArray(); + const fdict = await fs.getMetaDict(ufdocs.map((ufdoc) => ufdoc._id)); + this.response.template = 'home_file.html'; + this.response.body = { ufdocs, fdict }; + } + + async postDelete(ufid) { + const ufdoc = await file.get(ufid); + if (ufdoc.owner !== this.user._id) this.checkPriv(PRIV_DELETE_FILE); + const result = await file.del(ufdoc._id); + if (result) await file.decUsage(this.user._id, ufdoc.length); + this.back(); + } +} + async function apply() { Route('homepage', '/', HomeHandler); - Route('home_security', '/home/security', HomeSecurityHandler); - Route('user_changemail_with_code', '/home/changeMail/:code', UserChangemailWithCodeHandler); - Route('home_settings', '/home/settings/:category', HomeSettingsHandler); - Route('home_messages', '/home/messages', HomeMessagesHandler); + Route('home_security', '/home/security', HomeSecurityHandler, PERM_LOGGEDIN); + Route('user_changemail_with_code', '/home/changeMail/:code', UserChangemailWithCodeHandler, PERM_LOGGEDIN); + Route('home_settings', '/home/settings/:category', HomeSettingsHandler, PERM_LOGGEDIN); + Route('home_messages', '/home/messages', HomeMessagesHandler, PERM_LOGGEDIN); Connection('home_messages_conn', '/home/messages-conn', HomeMessagesConnectionHandler); } global.Hydro.handler.home = module.exports = apply; - -/* -@app.route('/home/file', 'home_file', global_route=True) -class HomeFileHandler(base.OperationHandler): - def file_url(this, fdoc): - return options.cdn_prefix.rstrip('/') + \ - this.reverse_url('fs_get', domain_id=builtin.DOMAIN_ID_SYSTEM, - secret=fdoc['metadata']['secret']) - - @base.require_priv(builtin.PRIV_USER_PROFILE) - async def get(this): - ufdocs = await userfile.get_multi(owner_uid=this.user['_id']).to_list() - fdict = await fs.get_meta_dict(ufdoc.get('file_id') for ufdoc in ufdocs) - this.render('home_file.html', ufdocs=ufdocs, fdict=fdict) - - @base.require_priv(builtin.PRIV_USER_PROFILE) - @base.post_argument - @base.require_csrfToken - @base.sanitize - async def post_delete(this, *, ufid: document.convert_doc_id): - ufdoc = await userfile.get(ufid) - if not this.own(ufdoc, priv=builtin.PRIV_DELETE_FILE_this): - this.check_priv(builtin.PRIV_DELETE_FILE) - result = await userfile.delete(ufdoc['doc_id']) - if result: - await userfile.dec_usage(this.user['_id'], ufdoc['length']) - this.redirect(this.referer_or_main) -*/ diff --git a/hydro/handler/user.js b/hydro/handler/user.js index 2abb0ff84..cab9db9b6 100644 --- a/hydro/handler/user.js +++ b/hydro/handler/user.js @@ -3,6 +3,8 @@ const moment = require('moment-timezone'); const { Route, Handler } = require('../service/server.js'); const user = require('../model/user'); const token = require('../model/token'); +const record = require('../model/record'); +const problem = require('../model/problem'); const task = require('../model/task'); const system = require('../model/system'); const { sendMail } = require('../lib/mail'); @@ -149,8 +151,14 @@ class UserDetailHandler extends Handler { const isSelfProfile = this.user._id === uid; const udoc = await user.getById(domainId, uid, true); const sdoc = await token.getMostRecentSessionByUid(uid); + const rdocs = await record.getByUid(domainId, uid); + const pdict = await problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false); + // Remove sensitive data + if (!isSelfProfile) sdoc.createIp = sdoc.updateIp = sdoc._id = ''; this.response.template = 'user_detail.html'; - this.response.body = { isSelfProfile, udoc, sdoc }; + this.response.body = { + isSelfProfile, udoc, sdoc, rdocs, pdict, + }; } } @@ -177,7 +185,7 @@ class UserSearchHandler extends Handler { if (udoc) udocs.push(udoc); for (const i in udocs) { if (udocs[i].gravatar) { - udocs[i].gravatar_url = misc.gravatar(udocs[i].gravatar); + udocs[i].gravatar = misc.gravatar(udocs[i].gravatar); } } this.response.body = udocs; @@ -248,10 +256,9 @@ class OauthCallbackHandler extends Handler { } async google({ - code, scope, authuser, prompt, error, state, + code, error, state, }) { if (error) throw new UserFacingError(error); - console.log(scope, authuser, prompt); const [ [appid, secret, url, proxy], s, diff --git a/hydro/model/message.js b/hydro/model/message.js index 5d4a1ef4e..faddd2e57 100644 --- a/hydro/model/message.js +++ b/hydro/model/message.js @@ -4,9 +4,12 @@ const db = require('../service/db.js'); const coll = db.collection('message'); async function send(from, to, content) { - await coll.insertOne({ + const res = await coll.insertOne({ from, to, content, unread: true, }); + return { + from, to, content, unread: true, _id: res.insertedId, + }; } async function get(_id) { @@ -16,7 +19,7 @@ async function get(_id) { } function getByUser(uid) { - return coll.find({ $or: [{ from: uid }, { to: uid }] }).toArray(); + return coll.find({ $or: [{ from: uid }, { to: uid }] }).sort('_id', 1).toArray(); } function getMany(query, sort, page, limit) { diff --git a/hydro/service/server.js b/hydro/service/server.js index a19632351..ba8d7658d 100644 --- a/hydro/service/server.js +++ b/hydro/service/server.js @@ -13,6 +13,7 @@ const sockjs = require('sockjs'); const http = require('http'); const validator = require('../lib/validator'); const template = require('../lib/template'); +const misc = require('../lib/misc'); const user = require('../model/user'); const system = require('../model/system'); const blacklist = require('../model/blacklist'); @@ -157,6 +158,8 @@ class Handler { async renderHTML(name, context) { if (enableLog) console.time(name); this.hasPerm = (perm) => this.user.hasPerm(perm); + // FIXME fix this ugly hack + this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; const res = await template.render(name, Object.assign(context, { handler: this, url: (...args) => this.url(...args), @@ -359,6 +362,7 @@ async function handle(ctx, HandlerClass, permission) { const args = { domainId: 'system', ...ctx.params, ...ctx.query, ...ctx.request.body, }; + h.args = args; if (h.___prepare) await h.___prepare(args); if (permission) h.checkPerm(permission); @@ -375,7 +379,6 @@ async function handle(ctx, HandlerClass, permission) { if (e instanceof ValidationError) throw e; throw new ValidationError(`Argument ${checking} check failed`); } - h.args = args; if (h.__prepare) await h.__prepare(args); if (h._prepare) await h._prepare(args); if (h.prepare) await h.prepare(args); @@ -440,6 +443,7 @@ class ConnectionHandler { async renderHTML(name, context) { this.hasPerm = (perm) => this.user.hasPerm(perm); + this._user = { ...this.user, gravatar: misc.gravatar(this.user.gravatar, 128) }; const res = await template.render(name, Object.assign(context, { handler: this, url: (...args) => this.url(...args), diff --git a/templates/layout/html5.html b/templates/layout/html5.html index 8d242b9ff..21f552599 100644 --- a/templates/layout/html5.html +++ b/templates/layout/html5.html @@ -23,13 +23,13 @@ - {{ handler.renderTitle(page_title) }} + {{ handler.renderTitle(page_name) }} {% if not isIE(handler.request.headers['user-agent']) %} {% endif %} diff --git a/ui/components/messagepad/MessagePadDialogueContentContainer.js b/ui/components/messagepad/MessagePadDialogueContentContainer.js index 3c65e725c..7a4edff9b 100644 --- a/ui/components/messagepad/MessagePadDialogueContentContainer.js +++ b/ui/components/messagepad/MessagePadDialogueContentContainer.js @@ -7,6 +7,7 @@ import 'jquery-scroll-lock'; import 'jquery.easing'; import i18n from 'vj/utils/i18n'; +import { parse as parseMongoId } from 'vj/utils/mongoId'; import Message from './MessageComponent'; const mapStateToProps = (state) => ({ @@ -22,24 +23,17 @@ export default class MessagePadDialogueContentContainer extends React.PureCompon $(this.refs.list).scrollLock({ strict: true }); } - UNSAFE_componentWillUpdate(nextProps) { // eslint-disable-line camelcase - if (nextProps.activeId !== this.props.activeId) { + componentDidUpdate(prevProps) { + const node = this.refs.list; + if (this.props.activeId !== prevProps.activeId) { this.scrollToBottom = true; this.scrollWithAnimation = false; - return; - } - const node = this.refs.list; - if (node.scrollTop + node.offsetHeight === node.scrollHeight) { + } else if (node.scrollTop + node.offsetHeight === node.scrollHeight) { this.scrollToBottom = true; this.scrollWithAnimation = true; - return; - } - this.scrollToBottom = false; - } + } else this.scrollToBottom = false; - componentDidUpdate() { if (this.scrollToBottom) { - const node = this.refs.list; const targetScrollTop = node.scrollHeight - node.offsetHeight; if (this.scrollWithAnimation) { $(node).stop().animate({ scrollTop: targetScrollTop }, 200, 'easeOutCubic'); @@ -53,19 +47,19 @@ export default class MessagePadDialogueContentContainer extends React.PureCompon if (this.props.activeId === null) { return []; } - return _.map(this.props.item.reply, (reply, idx) => ( + return _.map(this.props.item.messages, (reply) => (
{reply.content}
-
)); diff --git a/ui/components/messagepad/MessagePadDialogueListContainer.js b/ui/components/messagepad/MessagePadDialogueListContainer.js index 3da2e4e03..20f7a6cc5 100644 --- a/ui/components/messagepad/MessagePadDialogueListContainer.js +++ b/ui/components/messagepad/MessagePadDialogueListContainer.js @@ -28,9 +28,9 @@ export default class MessagePadDialogueListContainer extends React.PureComponent render() { const orderedDialogues = _.orderBy( _.values(this.props.dialogues), - (dialogue) => (dialogue.isPlaceholder - ? Number.POSITIVE_INFINITY - : _.maxBy(dialogue.reply, 'at').at), + (dialogue) => (_.maxBy(dialogue.messages, '_id') + ? _.maxBy(dialogue.messages, '_id')._id + : Number.POSITIVE_INFINITY), 'desc', ); return ( @@ -38,21 +38,9 @@ export default class MessagePadDialogueListContainer extends React.PureComponent {_.map(orderedDialogues, (dialogue) => ( this.props.handleClick(dialogue._id)} /> diff --git a/ui/components/messagepad/MessagePadInputContainer.js b/ui/components/messagepad/MessagePadInputContainer.js index 7a7aa1c5d..624733187 100644 --- a/ui/components/messagepad/MessagePadInputContainer.js +++ b/ui/components/messagepad/MessagePadInputContainer.js @@ -27,8 +27,7 @@ const mapDispatchToProps = (dispatch) => ({ }, }); }, - postSend(placeholderId, uid, value) { - if (placeholderId === null) return; + postSend(uid, value) { const req = request.post('', { operation: 'send', uid, @@ -38,23 +37,7 @@ const mapDispatchToProps = (dispatch) => ({ type: 'DIALOGUES_POST_SEND', payload: req, meta: { - placeholderId, - }, - }); - }, - postReply(_id, uid, value) { - if (_id === null) return; - const req = request.post('', { - operation: 'send', - type: 'single', - uid, - content: value, - }); - dispatch({ - type: 'DIALOGUES_POST_REPLY', - payload: req, - meta: { - dialogueId: _id, + dialogueId: uid, }, }); }, @@ -88,20 +71,10 @@ export default class MessagePadInputContainer extends React.PureComponent { } submit() { - const state = this.context.store.getState(); - if (state.dialogues[this.props.activeId].isPlaceholder) { - this.props.postSend( - this.props.activeId, - state.dialogues[this.props.activeId].to, - this.props.inputValue, - ); - } else { - this.props.postReply( - this.props.activeId, - state.dialogues[this.props.activeId].to, - this.props.inputValue, - ); - } + this.props.postSend( + this.props.activeId, + this.props.inputValue, + ); } render() { diff --git a/ui/components/messagepad/reducers/activeId.js b/ui/components/messagepad/reducers/activeId.js index 0600cb78d..036642c14 100644 --- a/ui/components/messagepad/reducers/activeId.js +++ b/ui/components/messagepad/reducers/activeId.js @@ -26,7 +26,7 @@ export default function reducer(state = null, action) { return action.payload; } case 'DIALOGUES_POST_SEND_FULFILLED': { - return action.payload.mdoc._id; + return action.meta.dialogueId; } default: return state; diff --git a/ui/components/messagepad/reducers/dialogues.js b/ui/components/messagepad/reducers/dialogues.js index 639d17bd7..25c53eb78 100644 --- a/ui/components/messagepad/reducers/dialogues.js +++ b/ui/components/messagepad/reducers/dialogues.js @@ -3,79 +3,48 @@ import _ from 'lodash'; export default function reducer(state = {}, action) { switch (action.type) { case 'DIALOGUES_LOAD_DIALOGUES_FULFILLED': { - const { messages, udict } = action.payload; - return _.keyBy( - _.filter( - _.map(messages, (m) => ({ - to_udoc: udict[m.to], - from_udoc: udict[m.from], - ...m, - })), - (m) => m.to_udoc && m.from_udoc, - ), - '_id', - ); + const { messages } = action.payload; + return _.keyBy(messages, 'udoc._id'); } case 'DIALOGUES_CREATE': { - const { id, user } = action.payload; + const { user } = action.payload; return { ...state, - [id]: { - _id: id, - from: UserContext._id, - to: user._id, - to_udoc: { ...user }, - reply: [], - isPlaceholder: true, - }, - }; - } - case 'DIALOGUES_POST_REPLY_FULFILLED': { - const id = action.meta.dialogueId; - const { reply } = action.payload; - return { - ...state, - [id]: { - ...state[id], - reply: [ - ...state[id].reply, - reply, - ], + [user._id]: { + _id: user._id, + udoc: { ...user }, + messages: [], }, }; } case 'DIALOGUES_POST_SEND_FULFILLED': { - const { placeholderId } = action.meta; + const id = action.meta.dialogueId; + const { mdoc } = action.payload; return { - ..._.omit(state, placeholderId), - [action.payload.mdoc._id]: action.payload.mdoc, + ...state, + [id]: { + ...state[id], + messages: [ + ...state[id].messages, + mdoc, + ], + }, }; } case 'DIALOGUES_MESSAGE_PUSH': { - const { type, data } = action.payload; - const id = data._id; - if (type === 'new') { - return { - ...state, - [id]: data, - }; - } if (type === 'reply') { - if (state[id] === undefined) { - window.location.reload(); - return state; - } - return { - ...state, - [id]: { - ...state[id], - reply: [ - ...state[id].reply, - data.reply[0], - ], - }, - }; - } - return state; + const { mdoc, udoc } = action.payload; + const to = mdoc.from === UserContext._id ? mdoc.to : mdoc.from; + return { + ...state, + [to]: { + ...state[to] || {}, + udoc, + messages: [ + ...state[to]?.messages || [], + mdoc, + ], + }, + }; } default: return state; diff --git a/ui/components/messagepad/reducers/inputs.js b/ui/components/messagepad/reducers/inputs.js index 7c20b5ac9..3df0bc0f9 100644 --- a/ui/components/messagepad/reducers/inputs.js +++ b/ui/components/messagepad/reducers/inputs.js @@ -7,10 +7,10 @@ export default function reducer(state = {}, action) { return _.fromPairs(_.map(dialogues, (d) => [d._id, ''])); } case 'DIALOGUES_CREATE': { - const { id } = action.payload; + const { user } = action.payload; return { ...state, - [id]: '', + [user._id]: '', }; } case 'DIALOGUES_INPUT_CHANGED': { @@ -20,27 +20,19 @@ export default function reducer(state = {}, action) { [id]: action.payload, }; } - case 'DIALOGUES_POST_REPLY_FULFILLED': { + case 'DIALOGUES_POST_SEND_FULFILLED': { const id = action.meta.dialogueId; return { ...state, [id]: '', }; } - case 'DIALOGUES_POST_SEND_FULFILLED': { - const { placeholderId } = action.meta; - return { - ..._.omit(state, placeholderId), - [action.payload.mdoc._id]: '', - }; - } case 'DIALOGUES_MESSAGE_PUSH': { - const { type, data } = action.payload; - const id = data._id; - if (type === 'new') { + const { udoc } = action.payload; + if (!state[udoc._id]) { return { ...state, - [id]: '', + [udoc._id]: '', }; } return state; diff --git a/ui/components/messagepad/reducers/isPosting.js b/ui/components/messagepad/reducers/isPosting.js index 22fffc825..722f821aa 100644 --- a/ui/components/messagepad/reducers/isPosting.js +++ b/ui/components/messagepad/reducers/isPosting.js @@ -8,66 +8,40 @@ export default function reducer(state = {}, action) { return _.fromPairs(_.map(dialogues, (d) => [d._id, false])); } case 'DIALOGUES_CREATE': { - const { id } = action.payload; + const { user } = action.payload; return { ...state, - [id]: false, + [user._id]: false, }; } - case 'DIALOGUES_POST_REPLY_PENDING': { + case 'DIALOGUES_POST_SEND_PENDING': { const id = action.meta.dialogueId; return { ...state, [id]: true, }; } - case 'DIALOGUES_POST_REPLY_REJECTED': { - Notification.error(action.payload.message); - const id = action.meta.dialogueId; - return { - ...state, - [id]: false, - }; - } - case 'DIALOGUES_POST_REPLY_FULFILLED': { - const id = action.meta.dialogueId; - return { - ...state, - [id]: false, - }; - } - case 'DIALOGUES_POST_SEND_PENDING': { - const { placeholderId } = action.meta; - return { - ...state, - [placeholderId]: true, - }; - } case 'DIALOGUES_POST_SEND_REJECTED': { Notification.error(action.payload.message); - const { placeholderId } = action.meta; + const id = action.meta.dialogueId; return { ...state, - [placeholderId]: false, + [id]: false, }; } case 'DIALOGUES_POST_SEND_FULFILLED': { - const { placeholderId } = action.meta; + const id = action.meta.dialogueId; return { - ..._.omit(state, placeholderId), - [action.payload.mdoc._id]: false, + ...state, + [id]: false, }; } case 'DIALOGUES_MESSAGE_PUSH': { - const { type, data } = action.payload; - const id = data._id; - if (type === 'new') { - return { - ...state, - [id]: false, - }; - } - return state; + const { udoc } = action.payload; + return { + ...state, + [udoc._id]: false, + }; } default: return state; diff --git a/ui/pages/home_messages.page.js b/ui/pages/home_messages.page.js index 44c499d32..c5ac62cf7 100644 --- a/ui/pages/home_messages.page.js +++ b/ui/pages/home_messages.page.js @@ -1,4 +1,3 @@ -import _ from 'lodash'; import { NamedPage } from 'vj/misc/PageLoader'; import request from 'vj/utils/request'; import loadReactRedux from 'vj/utils/loadReactRedux'; @@ -11,17 +10,15 @@ const page = new NamedPage('home_messages', () => { let reduxStore; function createDialog(user) { - const id = _.uniqueId('PLACEHOLDER_'); reduxStore.dispatch({ type: 'DIALOGUES_CREATE', payload: { - id, user, }, }); reduxStore.dispatch({ type: 'DIALOGUES_SWITCH_TO', - payload: id, + payload: user._id, }); } @@ -82,11 +79,11 @@ const page = new NamedPage('home_messages', () => { */ async function loadSendTarget() { const queryString = parseQueryString(); - if (!queryString.target_uid) { + if (!queryString.target) { return; } const user = await request.get('/user/search', { - q: queryString.target_uid, + q: queryString.target, exact_match: true, }); if (!user || user.length === 0) {