|
|
@@ -17,6 +17,7 @@ import { PageModel } from '../models/page';
|
|
|
import { serializeUserSecurely } from '../models/serializers/user-serializer';
|
|
|
|
|
|
import { ObjectIdLike } from '../interfaces/mongoose-utils';
|
|
|
+import { SearchError } from '../models/vo/error-search';
|
|
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
|
const logger = loggerFactory('growi:service:search');
|
|
|
@@ -39,6 +40,10 @@ const normalizeQueryString = (_queryString: string): string => {
|
|
|
return queryString;
|
|
|
};
|
|
|
|
|
|
+const normalizeNQName = (nqName: string): string => {
|
|
|
+ return nqName.trim();
|
|
|
+};
|
|
|
+
|
|
|
const findPageListByIds = async(pageIds: ObjectIdLike[], crowi: any) => {
|
|
|
|
|
|
const Page = crowi.model('Page') as unknown as PageModel;
|
|
|
@@ -127,7 +132,7 @@ class SearchService implements SearchQueryParser, SearchResolver {
|
|
|
generateNQDelegators(defaultDelegator: ElasticsearchDelegator): {[key in SearchDelegatorName]: SearchDelegator} {
|
|
|
return {
|
|
|
[SearchDelegatorName.DEFAULT]: defaultDelegator,
|
|
|
- [SearchDelegatorName.PRIVATE_LEGACY_PAGES]: new PrivateLegacyPagesDelegator() as SearchDelegator,
|
|
|
+ [SearchDelegatorName.PRIVATE_LEGACY_PAGES]: new PrivateLegacyPagesDelegator() as unknown as SearchDelegator,
|
|
|
};
|
|
|
}
|
|
|
|
|
|
@@ -218,36 +223,32 @@ class SearchService implements SearchQueryParser, SearchResolver {
|
|
|
return this.fullTextSearchDelegator.rebuildIndex();
|
|
|
}
|
|
|
|
|
|
- // TODO: https://redmine.weseek.co.jp/issues/92049 No need to parseNQString anymore
|
|
|
async parseSearchQuery(queryString: string, nqName: string | null): Promise<ParsedQuery> {
|
|
|
// eslint-disable-next-line no-param-reassign
|
|
|
queryString = normalizeQueryString(queryString);
|
|
|
|
|
|
- const regexp = new RegExp(/^\[nq:.+\]$/g); // https://regex101.com/r/FzDUvT/1
|
|
|
- const replaceRegexp = new RegExp(/\[nq:|\]/g);
|
|
|
+ const terms = this.parseQueryString(queryString);
|
|
|
|
|
|
- // when Normal Query
|
|
|
- if (!regexp.test(queryString)) {
|
|
|
- return { queryString, terms: this.parseQueryString(queryString) };
|
|
|
+ if (nqName == null) {
|
|
|
+ return { queryString, terms };
|
|
|
}
|
|
|
|
|
|
- // when Named Query
|
|
|
- const name = queryString.replace(replaceRegexp, '');
|
|
|
- const nq = await NamedQuery.findOne({ name });
|
|
|
+ const nq = await NamedQuery.findOne({ name: normalizeNQName(nqName) });
|
|
|
|
|
|
// will delegate to full-text search
|
|
|
if (nq == null) {
|
|
|
- return { queryString, terms: this.parseQueryString(queryString) };
|
|
|
+ logger.debug(`Delegated to full-text search since a named query document did not found. (nqName="${nqName}")`);
|
|
|
+ return { queryString, terms };
|
|
|
}
|
|
|
|
|
|
const { aliasOf, delegatorName } = nq;
|
|
|
|
|
|
- let parsedQuery;
|
|
|
+ let parsedQuery: ParsedQuery;
|
|
|
if (aliasOf != null) {
|
|
|
parsedQuery = { queryString: normalizeQueryString(aliasOf), terms: this.parseQueryString(aliasOf) };
|
|
|
}
|
|
|
- if (delegatorName != null) {
|
|
|
- parsedQuery = { queryString, delegatorName };
|
|
|
+ else {
|
|
|
+ parsedQuery = { queryString, terms, delegatorName };
|
|
|
}
|
|
|
|
|
|
return parsedQuery;
|
|
|
@@ -264,6 +265,24 @@ class SearchService implements SearchQueryParser, SearchResolver {
|
|
|
return [nqDeledator, data];
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Throws SearchError if data is corrupted.
|
|
|
+ * @param {SearchableData} data
|
|
|
+ * @param {SearchDelegator} delegator
|
|
|
+ * @throws {SearchError} SearchError
|
|
|
+ */
|
|
|
+ private validateSearchableData(delegator: SearchDelegator, data: SearchableData): void {
|
|
|
+ const { terms } = data;
|
|
|
+
|
|
|
+ if (delegator.isTermsNormalized(terms)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const unavailableTermsKeys = delegator.validateTerms(terms);
|
|
|
+
|
|
|
+ throw new SearchError('The query string includes unavailable terms.', unavailableTermsKeys);
|
|
|
+ }
|
|
|
+
|
|
|
async searchKeyword(keyword: string, nqName: string | null, user, userGroups, searchOpts): Promise<[ISearchResult<unknown>, string | null]> {
|
|
|
let parsedQuery: ParsedQuery;
|
|
|
// parse
|
|
|
@@ -286,6 +305,9 @@ class SearchService implements SearchQueryParser, SearchResolver {
|
|
|
throw err;
|
|
|
}
|
|
|
|
|
|
+ // throws
|
|
|
+ this.validateSearchableData(delegator, data);
|
|
|
+
|
|
|
return [await delegator.search(data, user, userGroups, searchOpts), delegator.name ?? null];
|
|
|
}
|
|
|
|