Browse Source

refactor: enhance createSearchQuery and initializeBoolQuery methods for better type safety and error handling

Shun Miyazawa 9 months ago
parent
commit
4565eb6cd1
1 changed files with 35 additions and 18 deletions
  1. 35 18
      apps/app/src/server/service/search-delegator/elasticsearch.ts

+ 35 - 18
apps/app/src/server/service/search-delegator/elasticsearch.ts

@@ -24,6 +24,7 @@ import type { UpdateOrInsertPagesOpts } from '../interfaces/search';
 
 
 import { aggregatePipelineToIndex } from './aggregate-to-index';
 import { aggregatePipelineToIndex } from './aggregate-to-index';
 import type { AggregatedPage, BulkWriteBody, BulkWriteCommand } from './bulk-write';
 import type { AggregatedPage, BulkWriteBody, BulkWriteCommand } from './bulk-write';
+import type { SearchQuery } from './elasticsearch-client-delegator';
 import {
 import {
   getClient,
   getClient,
   isES7ClientDelegator,
   isES7ClientDelegator,
@@ -603,7 +604,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
    * create search query for Elasticsearch
    * create search query for Elasticsearch
    * @returns {object} query object
    * @returns {object} query object
    */
    */
-  createSearchQuery() {
+  createSearchQuery(): SearchQuery {
     const fields = ['path', 'bookmark_count', 'comment_count', 'seenUsers_count', 'updated_at', 'tag_names', 'comments'];
     const fields = ['path', 'bookmark_count', 'comment_count', 'seenUsers_count', 'updated_at', 'tag_names', 'comments'];
 
 
     // sort by score
     // sort by score
@@ -611,7 +612,9 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
       index: this.aliasName,
       index: this.aliasName,
       _source: fields,
       _source: fields,
       body: {
       body: {
-        query: {}, // query
+        query: {
+          bool: {},
+        },
       },
       },
     };
     };
 
 
@@ -630,10 +633,9 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
     query.body.sort = { [sort]: { order } };
     query.body.sort = { [sort]: { order } };
   }
   }
 
 
-  initializeBoolQuery(query) {
-    // query is created by createSearchQuery()
-    if (!query.body.query.bool) {
-      query.body.query.bool = {};
+  initializeBoolQuery(query: SearchQuery): SearchQuery {
+    if (query?.body?.query?.bool == null) {
+      throw new Error('query.body.query.bool is not initialized');
     }
     }
 
 
     const isInitialized = (query) => { return !!query && Array.isArray(query) };
     const isInitialized = (query) => { return !!query && Array.isArray(query) };
@@ -650,14 +652,30 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
     return query;
     return query;
   }
   }
 
 
-  appendCriteriaForQueryString(query, parsedKeywords: ESQueryTerms): void {
+  appendCriteriaForQueryString(query: SearchQuery, parsedKeywords: ESQueryTerms): void {
     query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
     query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
 
 
+    if (query.body?.query?.bool == null) {
+      throw new Error('query.body.query.bool is not initialized');
+    }
+
+    if (query.body?.query?.bool.must == null || !Array.isArray(query.body?.query?.bool.must)) {
+      throw new Error('query.body.query.bool.must is not initialized');
+    }
+
+    if (query.body?.query?.bool.must_not == null || !Array.isArray(query.body?.query?.bool.must_not)) {
+      throw new Error('query.body.query.bool.must_not is not initialized');
+    }
+
+    if (query.body?.query?.bool.filter == null || !Array.isArray(query.body?.query?.bool.filter)) {
+      throw new Error('query.body.query.bool.filter is not initialized');
+    }
+
     if (parsedKeywords.match.length > 0) {
     if (parsedKeywords.match.length > 0) {
       const q = {
       const q = {
         multi_match: {
         multi_match: {
           query: parsedKeywords.match.join(' '),
           query: parsedKeywords.match.join(' '),
-          type: 'most_fields',
+          type: 'most_fields' as const,
           fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
           fields: ['path.ja^2', 'path.en^2', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
         },
         },
       };
       };
@@ -669,36 +687,35 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
         multi_match: {
         multi_match: {
           query: parsedKeywords.not_match.join(' '),
           query: parsedKeywords.not_match.join(' '),
           fields: ['path.ja', 'path.en', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
           fields: ['path.ja', 'path.en', 'body.ja', 'body.en', 'comments.ja', 'comments.en'],
-          operator: 'or',
+          operator: 'or' as const,
         },
         },
       };
       };
       query.body.query.bool.must_not.push(q);
       query.body.query.bool.must_not.push(q);
     }
     }
 
 
     if (parsedKeywords.phrase.length > 0) {
     if (parsedKeywords.phrase.length > 0) {
-      parsedKeywords.phrase.forEach((phrase) => {
+      for (const phrase of parsedKeywords.phrase) {
         const phraseQuery = {
         const phraseQuery = {
           multi_match: {
           multi_match: {
-            query: phrase, // each phrase is quoteted words like "This is GROWI"
-            type: 'phrase',
+            query: phrase,
+            type: 'phrase' as const,
             fields: [
             fields: [
-              // Not use "*.ja" fields here, because we want to analyze (parse) search words
               'path.raw^2',
               'path.raw^2',
               'body',
               'body',
               'comments',
               'comments',
             ],
             ],
           },
           },
         };
         };
-        query.body.query.bool.must.push(phraseQuery);
-      });
+        query.body.query.bool.must.push(phraseQuery); // TypeScript can track this better
+      }
     }
     }
 
 
     if (parsedKeywords.not_phrase.length > 0) {
     if (parsedKeywords.not_phrase.length > 0) {
-      parsedKeywords.not_phrase.forEach((phrase) => {
+      for (const phrase of parsedKeywords.not_phrase) {
         const notPhraseQuery = {
         const notPhraseQuery = {
           multi_match: {
           multi_match: {
             query: phrase, // each phrase is quoteted words
             query: phrase, // each phrase is quoteted words
-            type: 'phrase',
+            type: 'phrase' as const, // Add 'as const' to fix type error
             fields: [
             fields: [
               // Not use "*.ja" fields here, because we want to analyze (parse) search words
               // Not use "*.ja" fields here, because we want to analyze (parse) search words
               'path.raw^2',
               'path.raw^2',
@@ -707,7 +724,7 @@ class ElasticsearchDelegator implements SearchDelegator<Data, ESTermsKey, ESQuer
           },
           },
         };
         };
         query.body.query.bool.must_not.push(notPhraseQuery);
         query.body.query.bool.must_not.push(notPhraseQuery);
-      });
+      }
     }
     }
 
 
     if (parsedKeywords.prefix.length > 0) {
     if (parsedKeywords.prefix.length > 0) {