From 5c97bbf1ba923daef35fe9c5b3872420eb2e88be Mon Sep 17 00:00:00 2001 From: undefined Date: Fri, 17 Sep 2021 17:02:50 +0800 Subject: [PATCH] core: remove /user/search --- packages/hydrooj/package.json | 2 +- packages/hydrooj/src/handler/user.ts | 48 ++++++++----------- .../components/UserSelectAutoComplete.jsx | 10 +++- .../components/monaco/languages/markdown.ts | 11 +++-- packages/ui-default/package.json | 2 +- .../ui-default/pages/home_messages.page.jsx | 22 ++++----- .../ui-default/templates/user_detail.html | 2 +- packages/ui-default/utils/api.ts | 25 +++++++++- 8 files changed, 74 insertions(+), 48 deletions(-) diff --git a/packages/hydrooj/package.json b/packages/hydrooj/package.json index e06f9a244..d8d1fbcb3 100644 --- a/packages/hydrooj/package.json +++ b/packages/hydrooj/package.json @@ -1,6 +1,6 @@ { "name": "hydrooj", - "version": "2.35.9", + "version": "2.36.0", "bin": "bin/hydrooj.js", "main": "src/loader", "module": "src/loader", diff --git a/packages/hydrooj/src/handler/user.ts b/packages/hydrooj/src/handler/user.ts index 9982aba64..9cf7ad17c 100644 --- a/packages/hydrooj/src/handler/user.ts +++ b/packages/hydrooj/src/handler/user.ts @@ -31,8 +31,9 @@ registerValue('User', [ ['scope', 'Int'], ['role', 'String'], ['loginat', 'Date'], - ['regat', 'Date'], - ['priv', 'Int', 'User Privilege'], + ['regat', 'Date!'], + ['priv', 'Int!', 'User Privilege'], + ['avatarUrl', 'String'], ]); registerResolver('Query', 'user(id: Int, uname: String, mail: String)', 'User', (arg, ctx) => { @@ -43,12 +44,26 @@ registerResolver('Query', 'user(id: Int, uname: String, mail: String)', 'User', }, `Get a user by id, uname, or mail. Returns current user if no argument is provided.`); -registerResolver('Query', 'users(ids: [Int], search: String)', '[User]', async (arg, ctx) => { - if (arg.ids) { +registerResolver('Query', 'users(ids: [Int], search: String, limit: Int, exact: Boolean)', '[User]', async (arg, ctx) => { + if (arg.ids?.length) { const res = await user.getList(ctx.domainId, arg.ids); return Object.keys(res).map((id) => res[+id]); } - return arg.search ? await user.getPrefixList(ctx.domainId, arg.search) : []; + if (!arg.search) return []; + const udoc = await user.getById(ctx.domainId, +arg.search) + || await user.getByUname(ctx.domainId, arg.search) + || await user.getByEmail(ctx.domainId, arg.search); + const udocs: User[] = arg.exact + ? [] + : await user.getPrefixList(ctx.domainId, arg.search, Math.min(arg.limit || 10, 10)); + if (udoc && !udocs.find((i) => i._id === udoc._id)) { + udocs.pop(); + udocs.unshift(udoc); + } + for (const i in udocs) { + udocs[i].avatarUrl = avatar(udocs[i].avatar); + } + return udocs; }, 'Get a list of user by ids, or search users with the prefix.'); class UserLoginHandler extends Handler { @@ -275,28 +290,6 @@ class UserDeleteHandler extends Handler { } } -class UserSearchHandler extends Handler { - @param('q', Types.String) - @param('exectMatch', Types.Boolean) - async get(domainId: string, q: string, exactMatch = false) { - let udoc = await user.getById(domainId, +q); - const udocs: User[] = udoc ? [udoc] : []; - if (!udocs.length) { - udoc = await user.getByUname(domainId, q); - if (udoc) udocs.push(udoc); - else { - udoc = await user.getByEmail(domainId, q); - if (udoc) udocs.push(udoc); - } - } - if (!exactMatch) udocs.push(...await user.getPrefixList(domainId, q, 20)); - for (const i in udocs) { - udocs[i].avatarUrl = avatar(udocs[i].avatar); - } - this.response.body = udocs; - } -} - class OauthHandler extends Handler { @param('type', Types.String) async get(domainId: string, type: string) { @@ -355,7 +348,6 @@ export async function apply() { Route('user_logout', '/logout', UserLogoutHandler, PRIV.PRIV_USER_PROFILE); Route('user_lostpass', '/lostpass', UserLostPassHandler); Route('user_lostpass_with_code', '/lostpass/:code', UserLostPassWithCodeHandler); - Route('user_search', '/user/search', UserSearchHandler, PRIV.PRIV_USER_PROFILE); Route('user_delete', '/user/delete', UserDeleteHandler, PRIV.PRIV_USER_PROFILE); Route('user_detail', '/user/:uid', UserDetailHandler); } diff --git a/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.jsx b/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.jsx index fa83cbbca..3aa0f50f4 100644 --- a/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.jsx +++ b/packages/ui-default/components/autocomplete/components/UserSelectAutoComplete.jsx @@ -1,11 +1,17 @@ import React, { forwardRef } from 'react'; import PropTypes from 'prop-types'; -import request from 'vj/utils/request'; +import api, { e } from 'vj/utils/api'; import AutoComplete from './AutoComplete'; // eslint-disable-next-line prefer-arrow-callback const UserSelectAutoComplete = forwardRef(function UserSelectAutoComplete(props, ref) { - const itemsFn = (query) => request.get('/user/search', { q: query }); + const itemsFn = (query) => api(e` + users(search: ${query}) { + _id + uname + avatarUrl + } + `, ['data', 'users']); const itemText = (user) => user.uname || user; diff --git a/packages/ui-default/components/monaco/languages/markdown.ts b/packages/ui-default/components/monaco/languages/markdown.ts index 150c9347a..9c5a63ba5 100644 --- a/packages/ui-default/components/monaco/languages/markdown.ts +++ b/packages/ui-default/components/monaco/languages/markdown.ts @@ -1,8 +1,7 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import list from 'emojis-list'; import keyword from 'emojis-keywords'; -import request from 'vj/utils/request'; -import api from 'vj/utils/api'; +import api, { e } from 'vj/utils/api'; const qqEmojies = [ 'weixiao', @@ -168,7 +167,13 @@ monaco.languages.registerCompletionItemProvider('markdown', { endColumn: word.endColumn, }; if (prefix === '@') { - const users = await request.get(`/user/search?q=${word.word}`); + const users = await api(e` + users(search: ${word.word}) { + _id + uname + avatarUrl + } + `, ['data', 'users']); return { suggestions: users.map((i) => ({ label: `@${i.uname} (UID=${i._id})`, diff --git a/packages/ui-default/package.json b/packages/ui-default/package.json index 2cc313a01..0d6a4b8fa 100644 --- a/packages/ui-default/package.json +++ b/packages/ui-default/package.json @@ -1,6 +1,6 @@ { "name": "@hydrooj/ui-default", - "version": "4.21.5", + "version": "4.21.6", "author": "undefined ", "license": "AGPL-3.0", "main": "hydro.js", diff --git a/packages/ui-default/pages/home_messages.page.jsx b/packages/ui-default/pages/home_messages.page.jsx index 8f34eee3d..f669c7a16 100644 --- a/packages/ui-default/pages/home_messages.page.jsx +++ b/packages/ui-default/pages/home_messages.page.jsx @@ -1,5 +1,5 @@ import { NamedPage } from 'vj/misc/Page'; -import request from 'vj/utils/request'; +import api, { e } from 'vj/utils/api'; import loadReactRedux from 'vj/utils/loadReactRedux'; import parseQueryString from 'vj/utils/parseQueryString'; @@ -79,16 +79,16 @@ const page = new NamedPage('home_messages', () => { */ async function loadSendTarget() { const queryString = parseQueryString(); - if (!queryString.target) { - return; - } - const user = await request.get('/user/search', { - q: queryString.target, - exact_match: true, - }); - if (!user || user.length === 0) { - return; - } + if (!queryString.target) return; + const user = await api(e` + users(search: ${queryString.target}, exact: true) { + _id + uname + avatarUrl + mail + } + `, ['data', 'users']); + if (!user?.length) return; createDialog(user[0]); } diff --git a/packages/ui-default/templates/user_detail.html b/packages/ui-default/templates/user_detail.html index 07720d726..9c115158b 100644 --- a/packages/ui-default/templates/user_detail.html +++ b/packages/ui-default/templates/user_detail.html @@ -37,7 +37,7 @@ {% endif %} - + {% if udoc['mail'] %} diff --git a/packages/ui-default/utils/api.ts b/packages/ui-default/utils/api.ts index bcc71d923..e174502a6 100644 --- a/packages/ui-default/utils/api.ts +++ b/packages/ui-default/utils/api.ts @@ -1,3 +1,26 @@ import request from './request'; -export default (q: string) => request.post('/api', { query: q.trim() }); +export default async (q: string, path: string[] = []) => { + let query = q.trim(); + if (!query.startsWith('query')) query = `query{${query}}`; + const res = await request.post('/api', { query }); + if (res.errors) throw new Error(res.errors[0].message); + let cursor = res; + for (const p of path) { + cursor = cursor[p]; + if (!cursor) return undefined; + } + return cursor; +}; + +export const e = ( + pieces: TemplateStringsArray, + ...templates: (string | number)[] +) => { + let res = ''; + for (let i = 0; i < pieces.length; i++) { + res += pieces[i]; + if (templates[i]) res += JSON.stringify(templates[i]); + } + return res; +};