summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-10-17 16:26:35 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-10-17 16:26:35 +0900
commitdec69cc67b060eec244eb8d2df7dcd362359514b (patch)
treeb7c8358e2ef553051f6c39950cd0fbcf7d79de62 /src
parentUpdate CONTRIBUTING.md (diff)
downloadsharkey-dec69cc67b060eec244eb8d2df7dcd362359514b.tar.gz
sharkey-dec69cc67b060eec244eb8d2df7dcd362359514b.tar.bz2
sharkey-dec69cc67b060eec244eb8d2df7dcd362359514b.zip
enhance: ユーザー検索の精度を強化
Diffstat (limited to 'src')
-rw-r--r--src/client/components/form/radios.vue12
-rw-r--r--src/client/pages/explore.vue19
-rw-r--r--src/server/api/endpoints/users/search-by-username-and-host.ts9
-rw-r--r--src/server/api/endpoints/users/search.ts119
4 files changed, 98 insertions, 61 deletions
diff --git a/src/client/components/form/radios.vue b/src/client/components/form/radios.vue
index 1d3d80172a..998a738202 100644
--- a/src/client/components/form/radios.vue
+++ b/src/client/components/form/radios.vue
@@ -22,7 +22,6 @@ export default defineComponent({
}
},
render() {
- const label = this.$slots.desc();
let options = this.$slots.default();
// なぜかFragmentになることがあるため
@@ -31,7 +30,6 @@ export default defineComponent({
return h('div', {
class: 'novjtcto'
}, [
- h('div', { class: 'label' }, label),
...options.map(option => h(MkRadio, {
key: option.key,
value: option.props.value,
@@ -45,16 +43,6 @@ export default defineComponent({
<style lang="scss">
.novjtcto {
- > .label {
- font-size: 0.85em;
- padding: 0 0 8px 12px;
- user-select: none;
-
- &:empty {
- display: none;
- }
- }
-
&:first-child {
margin-top: 0;
}
diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue
index 2ca0668611..596bc1f0ed 100644
--- a/src/client/pages/explore.vue
+++ b/src/client/pages/explore.vue
@@ -65,13 +65,18 @@
</div>
<div v-else-if="tab === 'search'">
<div class="_isolated">
- <MkInput v-model="query" :debounce="true" type="search">
+ <MkInput v-model="searchQuery" :debounce="true" type="search">
<template #prefix><i class="fas fa-search"></i></template>
<template #label>{{ $ts.searchUser }}</template>
</MkInput>
+ <MkRadios v-model="searchScope">
+ <option value="local">{{ $ts.local }}</option>
+ <option value="remote">{{ $ts.remote }}</option>
+ <option value="both">{{ $ts.both }}</option>
+ </MkRadios>
</div>
- <XUserList v-if="query" class="_gap" :pagination="searchPagination" ref="search"/>
+ <XUserList v-if="searchQuery" class="_gap" :pagination="searchPagination" ref="search"/>
</div>
</div>
</MkSpacer>
@@ -83,6 +88,7 @@ import { computed, defineComponent } from 'vue';
import XUserList from '@client/components/user-list.vue';
import MkFolder from '@client/components/ui/folder.vue';
import MkInput from '@client/components/form/input.vue';
+import MkRadios from '@client/components/form/radios.vue';
import number from '@client/filters/number';
import * as os from '@client/os';
import * as symbols from '@client/symbols';
@@ -92,6 +98,7 @@ export default defineComponent({
XUserList,
MkFolder,
MkInput,
+ MkRadios,
},
props: {
@@ -158,14 +165,16 @@ export default defineComponent({
searchPagination: {
endpoint: 'users/search',
limit: 10,
- params: computed(() => (this.query && this.query !== '') ? {
- query: this.query
+ params: computed(() => (this.searchQuery && this.searchQuery !== '') ? {
+ query: this.searchQuery,
+ scope: this.searchScope,
} : null)
},
tagsLocal: [],
tagsRemote: [],
stats: null,
- query: null,
+ searchQuery: null,
+ searchScope: 'both',
num: number,
};
},
diff --git a/src/server/api/endpoints/users/search-by-username-and-host.ts b/src/server/api/endpoints/users/search-by-username-and-host.ts
index b9fbf48fb2..8fdc710658 100644
--- a/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -1,6 +1,8 @@
import $ from 'cafy';
import define from '../../define';
import { Users } from '@/models/index';
+import { Brackets } from 'typeorm';
+import { USER_ACTIVE_THRESHOLD } from '@/const';
export const meta = {
tags: ['users'],
@@ -64,8 +66,11 @@ export default define(meta, async (ps, me) => {
.where('user.host IS NULL')
.andWhere('user.isSuspended = FALSE')
.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' })
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
+ .andWhere(new Brackets(qb => { qb
+ .where('user.lastActiveDate IS NULL')
+ .orWhere('user.lastActiveDate > :activeThreshold', { activeThreshold: new Date(Date.now() - USER_ACTIVE_THRESHOLD) });
+ }))
+ .orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST')
.take(ps.limit!)
.skip(ps.offset)
.getMany();
diff --git a/src/server/api/endpoints/users/search.ts b/src/server/api/endpoints/users/search.ts
index 8011d90b3d..e4fbfedd10 100644
--- a/src/server/api/endpoints/users/search.ts
+++ b/src/server/api/endpoints/users/search.ts
@@ -2,6 +2,8 @@ import $ from 'cafy';
import define from '../../define';
import { UserProfiles, Users } from '@/models/index';
import { User } from '@/models/entities/user';
+import { Brackets } from 'typeorm';
+import { USER_ACTIVE_THRESHOLD } from '@/const';
export const meta = {
tags: ['users'],
@@ -23,9 +25,9 @@ export const meta = {
default: 10,
},
- localOnly: {
- validator: $.optional.bool,
- default: false,
+ scope: {
+ validator: $.optional.str.or(['local', 'remote', 'both']),
+ default: 'both',
},
detail: {
@@ -51,58 +53,91 @@ export default define(meta, async (ps, me) => {
let users: User[] = [];
if (isUsername) {
- users = await Users.createQueryBuilder('user')
- .where('user.host IS NULL')
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
+ const usernameQuery = Users.createQueryBuilder('user')
+ .where('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
+ .andWhere(new Brackets(qb => { qb
+ .where('user.lastActiveDate IS NULL')
+ .orWhere('user.lastActiveDate > :activeThreshold', { activeThreshold: new Date(Date.now() - USER_ACTIVE_THRESHOLD) });
+ }))
+ .andWhere('user.isSuspended = FALSE');
+
+ if (ps.scope === 'local') {
+ usernameQuery
+ .andWhere('user.host IS NULL')
+ .orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST');
+ } else if (ps.scope === 'remote') {
+ usernameQuery
+ .andWhere('user.host IS NOT NULL')
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
+ } else { // both
+ usernameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
+ }
+
+ users = await usernameQuery
.take(ps.limit!)
.skip(ps.offset)
.getMany();
+ } else {
+ const nameQuery = Users.createQueryBuilder('user')
+ .where('user.name ilike :query', { query: '%' + ps.query + '%' })
+ .andWhere(new Brackets(qb => { qb
+ .where('user.lastActiveDate IS NULL')
+ .orWhere('user.lastActiveDate > :activeThreshold', { activeThreshold: new Date(Date.now() - USER_ACTIVE_THRESHOLD) });
+ }))
+ .andWhere('user.isSuspended = FALSE');
- if (users.length < ps.limit! && !ps.localOnly) {
- const otherUsers = await Users.createQueryBuilder('user')
- .where('user.host IS NOT NULL')
- .andWhere('user.isSuspended = FALSE')
- .andWhere('user.usernameLower like :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
- .take(ps.limit! - users.length)
- .getMany();
-
- users = users.concat(otherUsers);
+ if (ps.scope === 'local') {
+ nameQuery
+ .andWhere('user.host IS NULL')
+ .orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST');
+ } else if (ps.scope === 'remote') {
+ nameQuery
+ .andWhere('user.host IS NOT NULL')
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
+ } else { // both
+ nameQuery
+ .orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
}
- } else {
- const profQuery = UserProfiles.createQueryBuilder('prof')
- .select('prof.userId')
- .where('prof.userHost IS NULL')
- .andWhere('prof.description ilike :query', { query: '%' + ps.query + '%' });
- users = await Users.createQueryBuilder('user')
- .where(`user.id IN (${ profQuery.getQuery() })`)
- .setParameters(profQuery.getParameters())
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
+ users = await nameQuery
.take(ps.limit!)
.skip(ps.offset)
.getMany();
- if (users.length < ps.limit! && !ps.localOnly) {
- const profQuery2 = UserProfiles.createQueryBuilder('prof')
+ if (users.length < ps.limit!) {
+ const profQuery = UserProfiles.createQueryBuilder('prof')
.select('prof.userId')
- .where('prof.userHost IS NOT NULL')
- .andWhere('prof.description ilike :query', { query: '%' + ps.query + '%' });
+ .where('prof.description ilike :query', { query: '%' + ps.query + '%' });
+
+ if (ps.scope === 'local') {
+ profQuery.andWhere('prof.userHost IS NULL');
+ } else if (ps.scope === 'remote') {
+ profQuery.andWhere('prof.userHost IS NOT NULL');
+ }
+
+ const query = Users.createQueryBuilder('user')
+ .where(`user.id IN (${ profQuery.getQuery() })`)
+ .andWhere(new Brackets(qb => { qb
+ .where('user.lastActiveDate IS NULL')
+ .orWhere('user.lastActiveDate > :activeThreshold', { activeThreshold: new Date(Date.now() - USER_ACTIVE_THRESHOLD) });
+ }))
+ .andWhere('user.isSuspended = FALSE')
+ .setParameters(profQuery.getParameters());
- const otherUsers = await Users.createQueryBuilder('user')
- .where(`user.id IN (${ profQuery2.getQuery() })`)
- .setParameters(profQuery2.getParameters())
- .andWhere('user.updatedAt IS NOT NULL')
- .orderBy('user.updatedAt', 'DESC')
- .take(ps.limit! - users.length)
- .getMany();
+ if (ps.scope === 'local') {
+ query.orderBy('user.lastActiveDate', 'DESC', 'NULLS LAST');
+ } else if (ps.scope === 'remote') {
+ query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
+ } else { // both
+ query.orderBy('user.updatedAt', 'DESC', 'NULLS LAST');
+ }
- users = users.concat(otherUsers);
+ users = users.concat(await query
+ .take(ps.limit!)
+ .skip(ps.offset)
+ .getMany()
+ );
}
}