[backend] Add in:bookmarks and in:favorites postgres FTS filters

This commit is contained in:
Laura Hausmann 2023-11-19 00:29:27 +01:00
parent 8c43c5cae6
commit 855409332b
No known key found for this signature in database
GPG Key ID: D044E84C5BE01605
3 changed files with 45 additions and 7 deletions

View File

@ -1487,6 +1487,7 @@ _filters:
title: "Advanced search filters" title: "Advanced search filters"
learnMore: "View advanced filters" learnMore: "View advanced filters"
wordFilters: "Filter by post text" wordFilters: "Filter by post text"
inFilters: "Filter by bookmark and/or favorite status"
miscFilters: "Filter by following relationship and/or note type" miscFilters: "Filter by following relationship and/or note type"
userDomain: "Filter by author, mentioned users, reply user or instance domain" userDomain: "Filter by author, mentioned users, reply user or instance domain"
postDate: "Filter by post date" postDate: "Filter by post date"

View File

@ -1,7 +1,7 @@
import { Brackets, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm"; import { Brackets, SelectQueryBuilder, WhereExpressionBuilder } from "typeorm";
import { sqlLikeEscape } from "@/misc/sql-like-escape.js"; import { sqlLikeEscape } from "@/misc/sql-like-escape.js";
import { sqlRegexEscape } from "@/misc/sql-regex-escape.js"; import { sqlRegexEscape } from "@/misc/sql-regex-escape.js";
import { Followings, Users } from "@/models/index.js"; import { Followings, NoteFavorites, NoteReactions, Users } from "@/models/index.js";
const filters = { const filters = {
"from": fromFilter, "from": fromFilter,
@ -24,11 +24,11 @@ const filters = {
"-host": instanceFilterInverse, "-host": instanceFilterInverse,
"filter": miscFilter, "filter": miscFilter,
"-filter": miscFilterInverse, "-filter": miscFilterInverse,
"in": inFilter,
"-in": inFilterInverse,
"has": attachmentFilter, "has": attachmentFilter,
} as Record<string, (query: SelectQueryBuilder<any>, search: string, id: number) => any> } as Record<string, (query: SelectQueryBuilder<any>, search: string, id: number) => any>
//TODO: editing the query should be possible, clicking search again resets it (it should be a twitter-like top of the page kind of deal)
export function generateFtsQuery(query: SelectQueryBuilder<any>, q: string): void { export function generateFtsQuery(query: SelectQueryBuilder<any>, q: string): void {
const components = q.trim().split(" "); const components = q.trim().split(" ");
const terms: string[] = []; const terms: string[] = [];
@ -156,11 +156,11 @@ function miscFilter(query: SelectQueryBuilder<any>, filter: string) {
if (filter === 'followers') { if (filter === 'followers') {
subQuery = Followings.createQueryBuilder('following') subQuery = Followings.createQueryBuilder('following')
.select('following.followerId') .select('following.followerId')
.where('following.followeeId = :meId') .where('following.followeeId = :meId');
} else if (filter === 'following') { } else if (filter === 'following') {
subQuery = Followings.createQueryBuilder('following') subQuery = Followings.createQueryBuilder('following')
.select('following.followeeId') .select('following.followeeId')
.where('following.followerId = :meId') .where('following.followerId = :meId');
} else if (filter === 'replies' || filter === 'reply') { } else if (filter === 'replies' || filter === 'reply') {
query.andWhere('note.replyId IS NOT NULL'); query.andWhere('note.replyId IS NOT NULL');
} else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') { } else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') {
@ -175,11 +175,11 @@ function miscFilterInverse(query: SelectQueryBuilder<any>, filter: string) {
if (filter === 'followers') { if (filter === 'followers') {
subQuery = Followings.createQueryBuilder('following') subQuery = Followings.createQueryBuilder('following')
.select('following.followerId') .select('following.followerId')
.where('following.followeeId = :meId') .where('following.followeeId = :meId');
} else if (filter === 'following') { } else if (filter === 'following') {
subQuery = Followings.createQueryBuilder('following') subQuery = Followings.createQueryBuilder('following')
.select('following.followeeId') .select('following.followeeId')
.where('following.followerId = :meId') .where('following.followerId = :meId');
} else if (filter === 'replies' || filter === 'reply') { } else if (filter === 'replies' || filter === 'reply') {
query.andWhere('note.replyId IS NULL'); query.andWhere('note.replyId IS NULL');
} else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') { } else if (filter === 'boosts' || filter === 'boost' || filter === 'renotes' || filter === 'renote') {
@ -189,6 +189,36 @@ function miscFilterInverse(query: SelectQueryBuilder<any>, filter: string) {
if (subQuery !== null) query.andWhere(`note.userId NOT IN (${subQuery.getQuery()})`); if (subQuery !== null) query.andWhere(`note.userId NOT IN (${subQuery.getQuery()})`);
} }
function inFilter(query: SelectQueryBuilder<any>, filter: string) {
let subQuery: SelectQueryBuilder<any> | null = null;
if (filter === 'bookmarks') {
subQuery = NoteFavorites.createQueryBuilder('bookmark')
.select('bookmark.noteId')
.where('bookmark.userId = :meId');
} else if (filter === 'favorites' || filter === 'favourites' || filter === 'reactions' || filter === 'likes') {
subQuery = NoteReactions.createQueryBuilder('react')
.select('react.noteId')
.where('react.userId = :meId');
}
if (subQuery !== null) query.andWhere(`note.id IN (${subQuery.getQuery()})`);
}
function inFilterInverse(query: SelectQueryBuilder<any>, filter: string) {
let subQuery: SelectQueryBuilder<any> | null = null;
if (filter === 'bookmarks') {
subQuery = NoteFavorites.createQueryBuilder('bookmark')
.select('bookmark.noteId')
.where('bookmark.userId = :meId');
} else if (filter === 'favorites' || filter === 'favourites' || filter === 'reactions' || filter === 'likes') {
subQuery = NoteReactions.createQueryBuilder('react')
.select('react.noteId')
.where('react.userId = :meId');
}
if (subQuery !== null) query.andWhere(`note.id NOT IN (${subQuery.getQuery()})`);
}
function attachmentFilter(query: SelectQueryBuilder<any>, filter: string) { function attachmentFilter(query: SelectQueryBuilder<any>, filter: string) {
switch(filter) { switch(filter) {
case 'image': case 'image':

View File

@ -34,6 +34,12 @@
<p><code>[-]instance:local|domain.tld</code></p> <p><code>[-]instance:local|domain.tld</code></p>
</div> </div>
</div> </div>
<div class="section _block">
<div class="title">{{ i18n.ts._filters._dialog.inFilters }}</div>
<div class="content">
<p><code>[-]in:bookmarks|favorites</code></p>
</div>
</div>
<div class="section _block"> <div class="section _block">
<div class="title">{{ i18n.ts._filters._dialog.miscFilters }}</div> <div class="title">{{ i18n.ts._filters._dialog.miscFilters }}</div>
<div class="content"> <div class="content">
@ -59,6 +65,7 @@
<div class="title">{{ i18n.ts._filters._dialog.infoEnd }}</div> <div class="title">{{ i18n.ts._filters._dialog.infoEnd }}</div>
<div class="content"> <div class="content">
<p>{{ i18n.ts._filters._dialog.infoEnd1 }}</p> <p>{{ i18n.ts._filters._dialog.infoEnd1 }}</p>
<p><code>in:likes = in:reactions = in:favourites = in:favorites</code></p>
<p><code>filter:reply = filter:replies</code></p> <p><code>filter:reply = filter:replies</code></p>
<p><code>search:word[s] = match:word[s]</code></p> <p><code>search:word[s] = match:word[s]</code></p>
<p><code>domain: = host: = instance:</code></p> <p><code>domain: = host: = instance:</code></p>