refactor search handler and service into multiple files
This commit is contained in:
parent
b98502284a
commit
1b8ba03c8b
11 changed files with 840 additions and 309 deletions
150
src/services/sigma/sigma_basic_search_service.js
Normal file
150
src/services/sigma/sigma_basic_search_service.js
Normal file
|
@ -0,0 +1,150 @@
|
|||
/**
|
||||
* sigma_basic_search_service.js
|
||||
*
|
||||
* Service for basic keyword searches of Sigma rules
|
||||
*/
|
||||
const { searchRules } = require('../../sigma_db/queries');
|
||||
const { convertSigmaRule } = require('./sigma_converter_service');
|
||||
const logger = require('../../utils/logger');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const { validateKeyword, validatePagination } = require('./utils/search_validation_utils');
|
||||
const { createPaginationInfo, createEmptyResponse } = require('./utils/search_pagination_utils');
|
||||
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Searches for Sigma rules by keyword
|
||||
* @param {string} keyword - Keyword to search for
|
||||
* @param {number} page - Page number (1-based)
|
||||
* @param {number} pageSize - Results per page
|
||||
* @returns {Promise<Object>} Search results with pagination
|
||||
*/
|
||||
async function searchSigmaRules(keyword, page = 1, pageSize = 10) {
|
||||
// Validate keyword
|
||||
const keywordValidation = validateKeyword(keyword);
|
||||
if (!keywordValidation.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: keywordValidation.message
|
||||
};
|
||||
}
|
||||
|
||||
// Validate pagination
|
||||
const { page: validatedPage, pageSize: validatedPageSize, offset } =
|
||||
validatePagination(page, pageSize);
|
||||
|
||||
logger.info(`${FILE_NAME}: Searching for Sigma rules with keyword: "${keywordValidation.trimmedKeyword}" (page ${validatedPage}, size ${validatedPageSize})`);
|
||||
|
||||
try {
|
||||
// Perform the database search
|
||||
const searchResult = await searchRules(keywordValidation.trimmedKeyword, validatedPageSize, offset);
|
||||
|
||||
// Extract results and total count
|
||||
let allResults = [];
|
||||
let totalCount = 0;
|
||||
|
||||
if (searchResult) {
|
||||
if (Array.isArray(searchResult)) {
|
||||
allResults = searchResult;
|
||||
totalCount = searchResult.length;
|
||||
} else if (typeof searchResult === 'object') {
|
||||
if (Array.isArray(searchResult.results)) {
|
||||
allResults = searchResult.results;
|
||||
totalCount = searchResult.totalCount || 0;
|
||||
} else if (searchResult.totalCount !== undefined) {
|
||||
totalCount = searchResult.totalCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Found ${allResults.length} results of ${totalCount} total`);
|
||||
|
||||
// Handle no results case
|
||||
if (allResults.length === 0 && totalCount === 0) {
|
||||
return createEmptyResponse(
|
||||
`No rules found matching "${keywordValidation.trimmedKeyword}"`,
|
||||
createPaginationInfo(validatedPage, validatedPageSize, 0)
|
||||
);
|
||||
}
|
||||
|
||||
// Create pagination info
|
||||
const pagination = createPaginationInfo(validatedPage, validatedPageSize, totalCount);
|
||||
|
||||
// Check for valid page number
|
||||
if (offset >= totalCount && totalCount > 0) {
|
||||
return createEmptyResponse(
|
||||
`No results on page ${validatedPage}. Try a previous page.`,
|
||||
pagination
|
||||
);
|
||||
}
|
||||
|
||||
// Return successful results
|
||||
return {
|
||||
success: true,
|
||||
results: allResults,
|
||||
count: allResults.length,
|
||||
pagination
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error searching for rules: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Error searching for rules: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches and converts Sigma rules (with full rule details)
|
||||
* @param {string} keyword - Keyword to search for
|
||||
* @param {number} page - Page number (1-based)
|
||||
* @param {number} pageSize - Results per page
|
||||
* @returns {Promise<Object>} Search results with converted rules
|
||||
*/
|
||||
async function searchAndConvertRules(keyword, page = 1, pageSize = 10) {
|
||||
try {
|
||||
// Perform the basic search
|
||||
const searchResult = await searchSigmaRules(keyword, page, pageSize);
|
||||
|
||||
if (!searchResult.success || !searchResult.results || searchResult.results.length === 0) {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Converting ${searchResult.results.length} search results to full rule objects`);
|
||||
|
||||
// Convert each rule to full rule object
|
||||
const convertedResults = [];
|
||||
for (const result of searchResult.results) {
|
||||
try {
|
||||
const conversionResult = await convertSigmaRule(result.id);
|
||||
if (conversionResult.success && conversionResult.rule) {
|
||||
convertedResults.push(conversionResult.rule);
|
||||
} else {
|
||||
logger.warn(`${FILE_NAME}: Failed to convert rule ${result.id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error converting rule ${result.id}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Return results with original pagination info
|
||||
return {
|
||||
success: true,
|
||||
results: convertedResults,
|
||||
count: convertedResults.length,
|
||||
originalCount: searchResult.results.length,
|
||||
pagination: searchResult.pagination
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error in searchAndConvertRules: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Error searching and converting rules: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchSigmaRules,
|
||||
searchAndConvertRules
|
||||
};
|
149
src/services/sigma/sigma_complex_search_service.js
Normal file
149
src/services/sigma/sigma_complex_search_service.js
Normal file
|
@ -0,0 +1,149 @@
|
|||
/**
|
||||
* sigma_complex_search_service.js
|
||||
*
|
||||
* Service for complex query searches of Sigma rules
|
||||
*/
|
||||
const { searchRulesComplex } = require('../../sigma_db/queries');
|
||||
const { parseComplexQuery } = require('../../lang/query_parser');
|
||||
const { convertSigmaRule } = require('./sigma_converter_service');
|
||||
const logger = require('../../utils/logger');
|
||||
const { getFileName } = require('../../utils/file_utils');
|
||||
const { validateQueryString, validatePagination } = require('./utils/search_validation_utils');
|
||||
const { createPaginationInfo, createEmptyResponse } = require('./utils/search_pagination_utils');
|
||||
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Performs a complex search for Sigma rules
|
||||
* @param {string} queryString - Complex query string
|
||||
* @param {number} page - Page number (1-based)
|
||||
* @param {number} pageSize - Results per page
|
||||
* @returns {Promise<Object>} Search results with pagination
|
||||
*/
|
||||
async function searchSigmaRulesComplex(queryString, page = 1, pageSize = 10) {
|
||||
// Validate query string
|
||||
const queryValidation = validateQueryString(queryString);
|
||||
if (!queryValidation.isValid) {
|
||||
return {
|
||||
success: false,
|
||||
message: queryValidation.message
|
||||
};
|
||||
}
|
||||
|
||||
// Validate pagination
|
||||
const { page: validatedPage, pageSize: validatedPageSize, offset } =
|
||||
validatePagination(page, pageSize);
|
||||
|
||||
logger.info(`${FILE_NAME}: Performing complex search with query: "${queryValidation.trimmedQuery}" (page ${validatedPage}, size ${validatedPageSize})`);
|
||||
|
||||
try {
|
||||
// Parse the complex query
|
||||
const parsedQuery = parseComplexQuery(queryValidation.trimmedQuery);
|
||||
|
||||
if (!parsedQuery.valid) {
|
||||
logger.warn(`${FILE_NAME}: Invalid complex query: ${parsedQuery.error}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Invalid query: ${parsedQuery.error}`
|
||||
};
|
||||
}
|
||||
|
||||
// Execute complex search
|
||||
const searchResult = await searchRulesComplex(parsedQuery, validatedPageSize, offset);
|
||||
|
||||
// Extract results and total count
|
||||
let allResults = [];
|
||||
let totalCount = 0;
|
||||
|
||||
if (searchResult) {
|
||||
if (Array.isArray(searchResult.results)) {
|
||||
allResults = searchResult.results;
|
||||
totalCount = searchResult.totalCount || 0;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Found ${allResults.length} results of ${totalCount} total`);
|
||||
|
||||
// Handle no results case
|
||||
if (allResults.length === 0) {
|
||||
return createEmptyResponse(
|
||||
'No rules found matching the complex query criteria',
|
||||
createPaginationInfo(validatedPage, validatedPageSize, totalCount)
|
||||
);
|
||||
}
|
||||
|
||||
// Create pagination info
|
||||
const pagination = createPaginationInfo(validatedPage, validatedPageSize, totalCount);
|
||||
|
||||
// Return successful results
|
||||
return {
|
||||
success: true,
|
||||
results: allResults,
|
||||
count: allResults.length,
|
||||
query: parsedQuery,
|
||||
pagination
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error in complex search: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Error performing complex search: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs complex search and converts results to full rule objects
|
||||
* @param {string} queryString - Complex query string
|
||||
* @param {number} page - Page number (1-based)
|
||||
* @param {number} pageSize - Results per page
|
||||
* @returns {Promise<Object>} Search results with converted rules
|
||||
*/
|
||||
async function searchAndConvertRulesComplex(queryString, page = 1, pageSize = 10) {
|
||||
try {
|
||||
// Perform complex search
|
||||
const searchResult = await searchSigmaRulesComplex(queryString, page, pageSize);
|
||||
|
||||
if (!searchResult.success || !searchResult.results || searchResult.results.length === 0) {
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
logger.debug(`${FILE_NAME}: Converting ${searchResult.results.length} complex search results to full rule objects`);
|
||||
|
||||
// Convert each result to full rule object
|
||||
const convertedResults = [];
|
||||
for (const result of searchResult.results) {
|
||||
try {
|
||||
const conversionResult = await convertSigmaRule(result.id);
|
||||
if (conversionResult.success && conversionResult.rule) {
|
||||
convertedResults.push(conversionResult.rule);
|
||||
} else {
|
||||
logger.warn(`${FILE_NAME}: Failed to convert rule ${result.id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error converting rule ${result.id}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Return results with original pagination info
|
||||
return {
|
||||
success: true,
|
||||
results: convertedResults,
|
||||
count: convertedResults.length,
|
||||
originalCount: searchResult.results.length,
|
||||
query: searchResult.query,
|
||||
pagination: searchResult.pagination
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error(`${FILE_NAME}: Error in searchAndConvertRulesComplex: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
message: `Error searching and converting rules: ${error.message}`
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
searchSigmaRulesComplex,
|
||||
searchAndConvertRulesComplex
|
||||
};
|
85
src/services/sigma/utils/search_pagination_utils.js
Normal file
85
src/services/sigma/utils/search_pagination_utils.js
Normal file
|
@ -0,0 +1,85 @@
|
|||
/**
|
||||
* search_pagination_utils.js
|
||||
*
|
||||
* Utility functions for handling search pagination
|
||||
*/
|
||||
const logger = require('../../../utils/logger');
|
||||
const { getFileName } = require('../../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Creates a standard pagination info object
|
||||
* @param {number} page - Current page number (1-based)
|
||||
* @param {number} pageSize - Size of each page
|
||||
* @param {number} totalCount - Total number of results
|
||||
* @returns {Object} Standardized pagination object
|
||||
*/
|
||||
function createPaginationInfo(page, pageSize, totalCount) {
|
||||
const totalPages = Math.ceil(totalCount / pageSize);
|
||||
const offset = (page - 1) * pageSize;
|
||||
const hasMore = (offset + pageSize) < totalCount;
|
||||
|
||||
return {
|
||||
currentPage: page,
|
||||
pageSize: pageSize,
|
||||
totalPages: totalPages,
|
||||
totalResults: totalCount,
|
||||
hasMore: hasMore
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts pagination parameters from command text
|
||||
* @param {string} text - The command text to parse
|
||||
* @returns {Object} Extracted parameters and cleaned text
|
||||
*/
|
||||
function extractPaginationParams(text) {
|
||||
if (!text) return { text: '', page: 1, pageSize: 10 };
|
||||
|
||||
let cleanedText = text.trim();
|
||||
let page = 1;
|
||||
let pageSize = 10;
|
||||
|
||||
// Extract page parameter
|
||||
const pagingMatch = cleanedText.match(/(.+)\s+page=(\d+)$/i);
|
||||
if (pagingMatch) {
|
||||
cleanedText = pagingMatch[1].trim();
|
||||
page = parseInt(pagingMatch[2], 10) || 1;
|
||||
logger.debug(`${FILE_NAME}: Extracted page ${page} from command text`);
|
||||
}
|
||||
|
||||
// Extract limit parameter
|
||||
const limitMatch = cleanedText.match(/(.+)\s+limit=(\d+)$/i);
|
||||
if (limitMatch) {
|
||||
cleanedText = limitMatch[1].trim();
|
||||
pageSize = parseInt(limitMatch[2], 10) || 10;
|
||||
logger.debug(`${FILE_NAME}: Extracted limit ${pageSize} from command text`);
|
||||
}
|
||||
|
||||
return {
|
||||
text: cleanedText,
|
||||
page,
|
||||
pageSize
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a standard response object for empty result sets
|
||||
* @param {string} message - Message to include in the response
|
||||
* @param {Object} pagination - Pagination info
|
||||
* @returns {Object} Standardized empty response
|
||||
*/
|
||||
function createEmptyResponse(message, pagination) {
|
||||
return {
|
||||
success: true,
|
||||
results: [],
|
||||
message,
|
||||
pagination
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createPaginationInfo,
|
||||
extractPaginationParams,
|
||||
createEmptyResponse
|
||||
};
|
105
src/services/sigma/utils/search_validation_utils.js
Normal file
105
src/services/sigma/utils/search_validation_utils.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/**
|
||||
* search_validation_utils.js
|
||||
*
|
||||
* Utility functions for validating search parameters
|
||||
*/
|
||||
const logger = require('../../../utils/logger');
|
||||
const { getFileName } = require('../../../utils/file_utils');
|
||||
const FILE_NAME = getFileName(__filename);
|
||||
|
||||
/**
|
||||
* Validates a search keyword
|
||||
* @param {string} keyword - The keyword to validate
|
||||
* @returns {Object} Validation result object
|
||||
*/
|
||||
function validateKeyword(keyword) {
|
||||
if (!keyword || typeof keyword !== 'string') {
|
||||
logger.warn(`${FILE_NAME}: Cannot search rules: Missing or invalid keyword`);
|
||||
return {
|
||||
isValid: false,
|
||||
message: 'Missing or invalid search keyword'
|
||||
};
|
||||
}
|
||||
|
||||
const trimmedKeyword = keyword.trim();
|
||||
if (trimmedKeyword.length === 0) {
|
||||
logger.warn(`${FILE_NAME}: Cannot search rules: Empty keyword after trimming`);
|
||||
return {
|
||||
isValid: false,
|
||||
message: 'Search keyword cannot be empty'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
trimmedKeyword
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates query string for complex search
|
||||
* @param {string} queryString - The query string to validate
|
||||
* @returns {Object} Validation result object
|
||||
*/
|
||||
function validateQueryString(queryString) {
|
||||
if (!queryString || typeof queryString !== 'string') {
|
||||
logger.warn(`${FILE_NAME}: Cannot perform complex search: Missing or invalid query string`);
|
||||
return {
|
||||
isValid: false,
|
||||
message: 'Missing or invalid complex query'
|
||||
};
|
||||
}
|
||||
|
||||
const trimmedQuery = queryString.trim();
|
||||
if (trimmedQuery.length === 0) {
|
||||
logger.warn(`${FILE_NAME}: Cannot perform complex search: Empty query after trimming`);
|
||||
return {
|
||||
isValid: false,
|
||||
message: 'Complex query cannot be empty'
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isValid: true,
|
||||
trimmedQuery
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates pagination parameters
|
||||
* @param {number} page - Page number (1-based)
|
||||
* @param {number} pageSize - Number of results per page
|
||||
* @param {number} maxPageSize - Maximum allowed page size
|
||||
* @returns {Object} Validated pagination parameters
|
||||
*/
|
||||
function validatePagination(page = 1, pageSize = 10, maxPageSize = 100) {
|
||||
let validatedPage = page;
|
||||
let validatedPageSize = pageSize;
|
||||
|
||||
if (typeof validatedPage !== 'number' || validatedPage < 1) {
|
||||
logger.warn(`${FILE_NAME}: Invalid page number: ${page}, defaulting to 1`);
|
||||
validatedPage = 1;
|
||||
}
|
||||
|
||||
if (typeof validatedPageSize !== 'number' || validatedPageSize < 1) {
|
||||
logger.warn(`${FILE_NAME}: Invalid page size: ${pageSize}, defaulting to 10`);
|
||||
validatedPageSize = 10;
|
||||
} else if (validatedPageSize > maxPageSize) {
|
||||
logger.warn(`${FILE_NAME}: Page size ${pageSize} exceeds max ${maxPageSize}, limiting to ${maxPageSize}`);
|
||||
validatedPageSize = maxPageSize;
|
||||
}
|
||||
|
||||
const offset = (validatedPage - 1) * validatedPageSize;
|
||||
|
||||
return {
|
||||
page: validatedPage,
|
||||
pageSize: validatedPageSize,
|
||||
offset
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateKeyword,
|
||||
validateQueryString,
|
||||
validatePagination
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue