search: add localOnly to web app queries, fix string+bool queries
localOnly: true in a queryType means it won't be sent to the server, but will be evaluated by the web app on the returned search results.
This commit is contained in:
+10
-3
@@ -43,7 +43,6 @@ func activityDTONameToField(field string) string {
|
|||||||
return "IP"
|
return "IP"
|
||||||
}
|
}
|
||||||
return "unknown"
|
return "unknown"
|
||||||
// Only these query types actually search the ActivityDTO data.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func activityTypeGetterNameToType(getter string) ActivityType {
|
func activityTypeGetterNameToType(getter string) ActivityType {
|
||||||
@@ -221,10 +220,18 @@ func matchReferrerAsQuery(jf *mediabrowser.MediaBrowser, query *badgerhold.Query
|
|||||||
criterion := andField(query, "Type")
|
criterion := andField(query, "Type")
|
||||||
query = criterion.MatchFunc(func(ra *badgerhold.RecordAccess) (bool, error) {
|
query = criterion.MatchFunc(func(ra *badgerhold.RecordAccess) (bool, error) {
|
||||||
act := ra.Record().(*Activity)
|
act := ra.Record().(*Activity)
|
||||||
if act.Type == ActivityCreation || act.SourceType == ActivityUser || !act.SourceIsUser() {
|
if act.Type != ActivityCreation || act.SourceType != ActivityUser || !act.SourceIsUser() {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
return strings.Contains(strings.ToLower(act.MustGetSourceUsername(jf)), strings.ToLower(q.Value.(string))), nil
|
sourceUsername := act.MustGetSourceUsername(jf)
|
||||||
|
if q.Class == BoolQuery {
|
||||||
|
val := sourceUsername != ""
|
||||||
|
if q.Value.(bool) == false {
|
||||||
|
val = !val
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
return strings.Contains(strings.ToLower(sourceUsername), strings.ToLower(q.Value.(string))), nil
|
||||||
})
|
})
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ const queries = (): { [field: string]: QueryType } => { return {
|
|||||||
getter: "title",
|
getter: "title",
|
||||||
bool: false,
|
bool: false,
|
||||||
string: true,
|
string: true,
|
||||||
date: false
|
date: false,
|
||||||
|
localOnly: true
|
||||||
},
|
},
|
||||||
"user": {
|
"user": {
|
||||||
name: window.lang.strings("usersMentioned"),
|
name: window.lang.strings("usersMentioned"),
|
||||||
|
|||||||
+66
-24
@@ -28,6 +28,7 @@ export interface QueryType {
|
|||||||
date: boolean;
|
date: boolean;
|
||||||
dependsOnElement?: string; // Format for querySelector
|
dependsOnElement?: string; // Format for querySelector
|
||||||
show?: boolean;
|
show?: boolean;
|
||||||
|
localOnly?: boolean // Indicates can't be performed server-side.
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SearchConfiguration {
|
export interface SearchConfiguration {
|
||||||
@@ -84,7 +85,8 @@ export abstract class Query {
|
|||||||
|
|
||||||
public abstract compare(subjectValue: any): boolean;
|
public abstract compare(subjectValue: any): boolean;
|
||||||
|
|
||||||
asDTO(): QueryDTO {
|
asDTO(): QueryDTO | null {
|
||||||
|
if (this.localOnly) return null;
|
||||||
let out = {} as QueryDTO;
|
let out = {} as QueryDTO;
|
||||||
out.field = this._subject.getter;
|
out.field = this._subject.getter;
|
||||||
out.operator = this._operator;
|
out.operator = this._operator;
|
||||||
@@ -100,6 +102,8 @@ export abstract class Query {
|
|||||||
compareItem(item: SearchableItem): boolean {
|
compareItem(item: SearchableItem): boolean {
|
||||||
return this.compare(this.getValueFromItem(item));
|
return this.compare(this.getValueFromItem(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get localOnly(): boolean { return this._subject.localOnly ? true : false; }
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BoolQuery extends Query {
|
export class BoolQuery extends Query {
|
||||||
@@ -134,8 +138,9 @@ export class BoolQuery extends Query {
|
|||||||
return ((subjectBool && this._value) || (!subjectBool && !this._value))
|
return ((subjectBool && this._value) || (!subjectBool && !this._value))
|
||||||
}
|
}
|
||||||
|
|
||||||
asDTO(): QueryDTO {
|
asDTO(): QueryDTO | null {
|
||||||
let out = super.asDTO();
|
let out = super.asDTO();
|
||||||
|
if (out === null) return null;
|
||||||
out.class = "bool";
|
out.class = "bool";
|
||||||
out.value = this._value;
|
out.value = this._value;
|
||||||
return out;
|
return out;
|
||||||
@@ -159,8 +164,9 @@ export class StringQuery extends Query {
|
|||||||
return subjectString.toLowerCase().includes(this._value);
|
return subjectString.toLowerCase().includes(this._value);
|
||||||
}
|
}
|
||||||
|
|
||||||
asDTO(): QueryDTO {
|
asDTO(): QueryDTO | null {
|
||||||
let out = super.asDTO();
|
let out = super.asDTO();
|
||||||
|
if (out === null) return null;
|
||||||
out.class = "string";
|
out.class = "string";
|
||||||
out.value = this._value;
|
out.value = this._value;
|
||||||
return out;
|
return out;
|
||||||
@@ -259,8 +265,9 @@ export class DateQuery extends Query {
|
|||||||
return subjectDate > temp;
|
return subjectDate > temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
asDTO(): QueryDTO {
|
asDTO(): QueryDTO | null {
|
||||||
let out = super.asDTO();
|
let out = super.asDTO();
|
||||||
|
if (out === null) return null;
|
||||||
out.class = "date";
|
out.class = "date";
|
||||||
out.value = this._value.attempt;
|
out.value = this._value.attempt;
|
||||||
return out;
|
return out;
|
||||||
@@ -372,6 +379,8 @@ export class Search {
|
|||||||
}
|
}
|
||||||
this._c.search.oninput((null as Event));
|
this._c.search.oninput((null as Event));
|
||||||
};
|
};
|
||||||
|
queries.push(q);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (queryFormat.string) {
|
if (queryFormat.string) {
|
||||||
@@ -384,6 +393,8 @@ export class Search {
|
|||||||
}
|
}
|
||||||
this._c.search.oninput((null as Event));
|
this._c.search.oninput((null as Event));
|
||||||
}
|
}
|
||||||
|
queries.push(q);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
if (queryFormat.date) {
|
if (queryFormat.date) {
|
||||||
let [parsedDate, op, isDate] = DateQuery.paramsFromString(split[1]);
|
let [parsedDate, op, isDate] = DateQuery.paramsFromString(split[1]);
|
||||||
@@ -398,37 +409,48 @@ export class Search {
|
|||||||
|
|
||||||
this._c.search.oninput((null as Event));
|
this._c.search.oninput((null as Event));
|
||||||
}
|
}
|
||||||
|
queries.push(q);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
// if (q != null) queries.push(q);
|
||||||
if (q != null) queries.push(q);
|
|
||||||
}
|
}
|
||||||
return [searchTerms, queries];
|
return [searchTerms, queries];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a list of identifiers (used as keys in items, values in ordering).
|
// Returns a list of identifiers (used as keys in items, values in ordering).
|
||||||
search = (query: string): string[] => {
|
searchParsed = (searchTerms: string[], queries: Query[]): string[] => {
|
||||||
let timer = this.timeSearches ? performance.now() : null;
|
|
||||||
this._c.filterArea.textContent = "";
|
|
||||||
|
|
||||||
let result: string[] = [...this._ordering];
|
let result: string[] = [...this._ordering];
|
||||||
// If we're in a server search already, the results are already correct.
|
// If we're in a server search already, the results are (probably) already correct.
|
||||||
if (this.inServerSearch) return result;
|
if (this.inServerSearch) {
|
||||||
|
let hasLocalOnlyQueries = false;
|
||||||
|
for (const q of queries) {
|
||||||
|
if (q.localOnly) {
|
||||||
|
hasLocalOnlyQueries = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasLocalOnlyQueries) return result;
|
||||||
|
// Continue on if really necessary
|
||||||
|
}
|
||||||
|
|
||||||
const [searchTerms, queries] = this.parseTokens(Search.tokenizeSearch(query));
|
// Normal searches can be evaluated by the server, so skip this if we've already ran one.
|
||||||
|
if (!this.inServerSearch) {
|
||||||
query = "";
|
for (let term of searchTerms) {
|
||||||
|
let cachedResult = [...result];
|
||||||
for (let term of searchTerms) {
|
for (let id of cachedResult) {
|
||||||
let cachedResult = [...result];
|
const u = this.items[id];
|
||||||
for (let id of cachedResult) {
|
if (!u.matchesSearch(term)) {
|
||||||
const u = this.items[id];
|
result.splice(result.indexOf(id), 1);
|
||||||
if (!u.matchesSearch(term)) {
|
}
|
||||||
result.splice(result.indexOf(id), 1);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let q of queries) {
|
for (let q of queries) {
|
||||||
this._c.filterArea.appendChild(q.asElement());
|
this._c.filterArea.appendChild(q.asElement());
|
||||||
|
// Skip if this query has already been performed by the server.
|
||||||
|
if (this.inServerSearch && !(q.localOnly)) continue;
|
||||||
|
|
||||||
let cachedResult = [...result];
|
let cachedResult = [...result];
|
||||||
if (q.subject.bool) {
|
if (q.subject.bool) {
|
||||||
for (let id of cachedResult) {
|
for (let id of cachedResult) {
|
||||||
@@ -466,7 +488,18 @@ export class Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a list of identifiers (used as keys in items, values in ordering).
|
||||||
|
search = (query: string): string[] => {
|
||||||
|
let timer = this.timeSearches ? performance.now() : null;
|
||||||
|
this._c.filterArea.textContent = "";
|
||||||
|
|
||||||
|
const [searchTerms, queries] = this.parseTokens(Search.tokenizeSearch(query));
|
||||||
|
|
||||||
|
let result = this.searchParsed(searchTerms, queries);
|
||||||
|
|
||||||
this._queries = queries;
|
this._queries = queries;
|
||||||
this._searchTerms = searchTerms;
|
this._searchTerms = searchTerms;
|
||||||
|
|
||||||
@@ -476,6 +509,11 @@ export class Search {
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postServerSearch performs local-only queries after a server search if necessary.
|
||||||
|
postServerSearch = () => {
|
||||||
|
this.searchParsed(this._searchTerms, this._queries);
|
||||||
|
};
|
||||||
|
|
||||||
showHideSearchOptionsHeader = () => {
|
showHideSearchOptionsHeader = () => {
|
||||||
let sortingBy = false;
|
let sortingBy = false;
|
||||||
@@ -642,12 +680,16 @@ export class Search {
|
|||||||
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
serverSearchParams = (searchTerms: string[], queries: Query[]): PaginatedReqDTO => {
|
||||||
let req: ServerSearchReqDTO = {
|
let req: ServerSearchReqDTO = {
|
||||||
searchTerms: searchTerms,
|
searchTerms: searchTerms,
|
||||||
queries: queries.map((q: Query) => q.asDTO()),
|
queries: [], // queries.map((q: Query) => q.asDTO()) won't work as localOnly queries return null
|
||||||
limit: -1,
|
limit: -1,
|
||||||
page: 0,
|
page: 0,
|
||||||
sortByField: this.sortField,
|
sortByField: this.sortField,
|
||||||
ascending: this.ascending
|
ascending: this.ascending
|
||||||
};
|
};
|
||||||
|
for (const q of queries) {
|
||||||
|
const dto = q.asDTO();
|
||||||
|
if (dto !== null) req.queries.push(dto);
|
||||||
|
}
|
||||||
return req;
|
return req;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+26
-4
@@ -306,16 +306,38 @@ func (q QueryDTO) AsFilter() Filter {
|
|||||||
return cmp.Compare(bool2int(a.NotifyThroughEmail), bool2int(q.Value.(bool))) == int(operator)
|
return cmp.Compare(bool2int(a.NotifyThroughEmail), bool2int(q.Value.(bool))) == int(operator)
|
||||||
}
|
}
|
||||||
case "last_active":
|
case "last_active":
|
||||||
return func(a *respUser) bool {
|
switch q.Class {
|
||||||
return q.Value.(DateAttempt).CompareUnix(a.LastActive) == int(operator)
|
case DateQuery:
|
||||||
|
return func(a *respUser) bool {
|
||||||
|
return q.Value.(DateAttempt).CompareUnix(a.LastActive) == int(operator)
|
||||||
|
}
|
||||||
|
case BoolQuery:
|
||||||
|
return func(a *respUser) bool {
|
||||||
|
val := a.LastActive != 0
|
||||||
|
if q.Value.(bool) == false {
|
||||||
|
val = !val
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "admin":
|
case "admin":
|
||||||
return func(a *respUser) bool {
|
return func(a *respUser) bool {
|
||||||
return cmp.Compare(bool2int(a.Admin), bool2int(q.Value.(bool))) == int(operator)
|
return cmp.Compare(bool2int(a.Admin), bool2int(q.Value.(bool))) == int(operator)
|
||||||
}
|
}
|
||||||
case "expiry":
|
case "expiry":
|
||||||
return func(a *respUser) bool {
|
switch q.Class {
|
||||||
return q.Value.(DateAttempt).CompareUnix(a.Expiry) == int(operator)
|
case DateQuery:
|
||||||
|
return func(a *respUser) bool {
|
||||||
|
return q.Value.(DateAttempt).CompareUnix(a.Expiry) == int(operator)
|
||||||
|
}
|
||||||
|
case BoolQuery:
|
||||||
|
return func(a *respUser) bool {
|
||||||
|
val := a.Expiry != 0
|
||||||
|
if q.Value.(bool) == false {
|
||||||
|
val = !val
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case "disabled":
|
case "disabled":
|
||||||
return func(a *respUser) bool {
|
return func(a *respUser) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user