diff --git a/Services/server/src/controllers/searchController.ts b/Services/server/src/controllers/searchController.ts index 37bd81a..37e7053 100644 --- a/Services/server/src/controllers/searchController.ts +++ b/Services/server/src/controllers/searchController.ts @@ -1,9 +1,10 @@ import { Body, Controller, Post, Route, Security, SuccessResponse, Tags } from "tsoa"; import HttpStatusCode from "../interfaces/http-status"; import esClient from "../elasticsearch"; -import { Search, SearchInfo, SearchOperator } from "../interfaces/search"; +import { Search, SearchInfo, SearchOperator, SearchOptions } from "../interfaces/search"; import { StorageFile } from "../interfaces/storage-fs"; import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types"; +import HttpError from "../interfaces/http-error"; const DEFAULT_INDEX = process.env.ELASTICSEARCH_INDEX; @@ -15,7 +16,26 @@ export class SearchController extends Controller { @Tags("Search") @Security("bearerAuth") @SuccessResponse(HttpStatusCode.OK, "สำเร็จ") - public async searchFile(@Body() search: Search): Promise { + public async searchFile( + @Body() + body: { + AND?: ( + | SearchInfo + | { + AND?: unknown; + OR?: unknown; + } + )[]; + OR?: ( + | SearchInfo + | { + AND?: unknown; + OR?: unknown; + } + )[]; + } & SearchOptions, + ): Promise { + const search = body as Search; const type = ["match", "match_phrase"] as const; const searchMapCallback = (v: SearchInfo | SearchOperator): QueryDslQueryContainer => { @@ -23,9 +43,16 @@ export class SearchController extends Controller { return { [type[search.exact || v.exact ? 1 : 0]]: { [v.field]: v.value } }; } - return { - bool: { must: v.AND?.map(searchMapCallback), should: v.OR?.map(searchMapCallback) }, - }; + if ("AND" in v || "OR" in v) { + return { + bool: { must: v.AND?.map(searchMapCallback), should: v.OR?.map(searchMapCallback) }, + }; + } + + throw new HttpError( + HttpStatusCode.UNPROCESSABLE_ENTITY, + "ข้อมูลค้นหาไม่ถูกต้อง กรุณาตรวจสอบอีกครั้ง", + ); }; const result = await esClient.search }>({ diff --git a/Services/server/src/interfaces/search.ts b/Services/server/src/interfaces/search.ts index c16fc63..8d3ecf5 100644 --- a/Services/server/src/interfaces/search.ts +++ b/Services/server/src/interfaces/search.ts @@ -1,5 +1,11 @@ import { QueryDslQueryContainer } from "@elastic/elasticsearch/lib/api/types"; +export interface SearchOptions { + recursive?: boolean; + path?: string[]; + exact?: boolean; +} + export interface SearchInfo { field: string; value: string; @@ -11,11 +17,7 @@ export interface SearchOperator { OR?: (SearchInfo | SearchOperator)[]; } -export interface Search extends SearchOperator { - recursive?: boolean; - path?: string[]; - exact?: boolean; -} +export type Search = SearchOperator & SearchOptions; export function mapCallback(exact: boolean) { const type = ["match", "match_phrase"] as const; diff --git a/Services/server/src/routes.ts b/Services/server/src/routes.ts index bd422a4..228a239 100644 --- a/Services/server/src/routes.ts +++ b/Services/server/src/routes.ts @@ -45,14 +45,22 @@ const models: TsoaRoute.Models = { "additionalProperties": false, }, // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa - "Search": { + "SearchInfo": { "dataType": "refObject", "properties": { - "AND": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"exact":{"dataType":"boolean"},"value":{"dataType":"string","required":true},"field":{"dataType":"string","required":true}}}}, - "OR": {"dataType":"array","array":{"dataType":"nestedObjectLiteral","nestedProperties":{"exact":{"dataType":"boolean"},"value":{"dataType":"string","required":true},"field":{"dataType":"string","required":true}}}}, + "field": {"dataType":"string","required":true}, + "value": {"dataType":"string","required":true}, "exact": {"dataType":"boolean"}, + }, + "additionalProperties": false, + }, + // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa + "SearchOptions": { + "dataType": "refObject", + "properties": { "recursive": {"dataType":"boolean"}, "path": {"dataType":"array","array":{"dataType":"string"}}, + "exact": {"dataType":"boolean"}, }, "additionalProperties": false, }, @@ -177,7 +185,7 @@ export function RegisterRoutes(app: Router) { function SearchController_searchFile(request: any, response: any, next: any) { const args = { - search: {"in":"body","name":"search","required":true,"ref":"Search"}, + body: {"in":"body","name":"body","required":true,"dataType":"intersection","subSchemas":[{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"ref":"SearchInfo"},{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"any"},"AND":{"dataType":"any"}}}]}},"AND":{"dataType":"array","array":{"dataType":"union","subSchemas":[{"ref":"SearchInfo"},{"dataType":"nestedObjectLiteral","nestedProperties":{"OR":{"dataType":"any"},"AND":{"dataType":"any"}}}]}}}},{"ref":"SearchOptions"}]}, }; // WARNING: This file was auto-generated with tsoa. Please do not modify it. Re-run tsoa to re-generate this file: https://github.com/lukeautry/tsoa diff --git a/Services/server/src/swagger.json b/Services/server/src/swagger.json index 32a47ea..85062be 100644 --- a/Services/server/src/swagger.json +++ b/Services/server/src/swagger.json @@ -110,53 +110,27 @@ "type": "object", "additionalProperties": false }, - "Search": { + "SearchInfo": { "properties": { - "AND": { - "items": { - "properties": { - "exact": { - "type": "boolean" - }, - "value": { - "type": "string" - }, - "field": { - "type": "string" - } - }, - "required": [ - "value", - "field" - ], - "type": "object" - }, - "type": "array" + "field": { + "type": "string" }, - "OR": { - "items": { - "properties": { - "exact": { - "type": "boolean" - }, - "value": { - "type": "string" - }, - "field": { - "type": "string" - } - }, - "required": [ - "value", - "field" - ], - "type": "object" - }, - "type": "array" + "value": { + "type": "string" }, "exact": { "type": "boolean" - }, + } + }, + "required": [ + "field", + "value" + ], + "type": "object", + "additionalProperties": false + }, + "SearchOptions": { + "properties": { "recursive": { "type": "boolean" }, @@ -165,6 +139,9 @@ "type": "string" }, "type": "array" + }, + "exact": { + "type": "boolean" } }, "type": "object", @@ -617,7 +594,50 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/Search" + "allOf": [ + { + "properties": { + "OR": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SearchInfo" + }, + { + "properties": { + "OR": {}, + "AND": {} + }, + "type": "object" + } + ] + }, + "type": "array" + }, + "AND": { + "items": { + "anyOf": [ + { + "$ref": "#/components/schemas/SearchInfo" + }, + { + "properties": { + "OR": {}, + "AND": {} + }, + "type": "object" + } + ] + }, + "type": "array" + } + }, + "type": "object" + }, + { + "$ref": "#/components/schemas/SearchOptions" + } + ] } } }