189 lines
4.8 KiB
JavaScript
189 lines
4.8 KiB
JavaScript
/**
|
|
* Meilisearch indexer for search functionality
|
|
*/
|
|
|
|
import meilisearch from '../config/meilisearch.js';
|
|
import { Service } from '../models/Service.js';
|
|
import { Analysis } from '../models/Analysis.js';
|
|
|
|
const INDEX_NAME = 'services';
|
|
|
|
export class SearchIndexer {
|
|
static index = null;
|
|
|
|
/**
|
|
* Initialize the search index
|
|
*/
|
|
static async init() {
|
|
try {
|
|
console.log('Initializing Meilisearch...');
|
|
|
|
// Check if index exists
|
|
const indexes = await meilisearch.getIndexes();
|
|
const indexExists = indexes.results.some(idx => idx.uid === INDEX_NAME);
|
|
|
|
if (!indexExists) {
|
|
console.log('Creating search index...');
|
|
await meilisearch.createIndex(INDEX_NAME, { primaryKey: 'id' });
|
|
}
|
|
|
|
this.index = meilisearch.index(INDEX_NAME);
|
|
|
|
// Configure searchable attributes
|
|
await this.index.updateSettings({
|
|
searchableAttributes: [
|
|
'name',
|
|
'findings.positive.title',
|
|
'findings.positive.description',
|
|
'findings.negative.title',
|
|
'findings.negative.description',
|
|
'data_types_collected',
|
|
'third_parties.name'
|
|
],
|
|
filterableAttributes: ['grade', 'overall_score'],
|
|
sortableAttributes: ['name', 'created_at', 'overall_score'],
|
|
rankingRules: [
|
|
'words',
|
|
'typo',
|
|
'proximity',
|
|
'attribute',
|
|
'sort',
|
|
'exactness'
|
|
]
|
|
});
|
|
|
|
console.log('Meilisearch initialized successfully');
|
|
} catch (error) {
|
|
console.error('Meilisearch initialization error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Index a single service
|
|
* @param {Object} service - Service with analysis
|
|
*/
|
|
static async indexService(service) {
|
|
try {
|
|
if (!this.index) {
|
|
await this.init();
|
|
}
|
|
|
|
const document = {
|
|
id: service.id,
|
|
name: service.name,
|
|
url: service.url,
|
|
logo_url: service.logo_url,
|
|
grade: service.grade,
|
|
overall_score: service.overall_score,
|
|
findings: service.findings || { positive: [], negative: [], neutral: [] },
|
|
data_types_collected: service.data_types_collected || [],
|
|
third_parties: service.third_parties || [],
|
|
last_analyzed: service.last_analyzed,
|
|
created_at: service.created_at
|
|
};
|
|
|
|
await this.index.addDocuments([document]);
|
|
console.log(`Indexed service: ${service.name}`);
|
|
} catch (error) {
|
|
console.error(`Failed to index service ${service.name}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Index all services
|
|
*/
|
|
static async indexAll() {
|
|
try {
|
|
console.log('Indexing all services...');
|
|
|
|
const services = await Service.findAllWithLatestAnalysis();
|
|
|
|
const documents = services.map(service => ({
|
|
id: service.id,
|
|
name: service.name,
|
|
url: service.url,
|
|
logo_url: service.logo_url,
|
|
grade: service.grade,
|
|
overall_score: service.overall_score,
|
|
findings: service.findings || { positive: [], negative: [], neutral: [] },
|
|
data_types_collected: service.data_types_collected || [],
|
|
third_parties: service.third_parties || [],
|
|
last_analyzed: service.last_analyzed,
|
|
created_at: service.created_at
|
|
}));
|
|
|
|
if (documents.length > 0) {
|
|
await this.index.addDocuments(documents);
|
|
console.log(`Indexed ${documents.length} services`);
|
|
}
|
|
|
|
return { indexed: documents.length };
|
|
} catch (error) {
|
|
console.error('Bulk indexing error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search services
|
|
* @param {string} query - Search query
|
|
* @param {Object} options - Search options
|
|
*/
|
|
static async search(query, options = {}) {
|
|
try {
|
|
if (!this.index) {
|
|
await this.init();
|
|
}
|
|
|
|
const searchOptions = {
|
|
limit: options.limit || 25,
|
|
offset: options.offset || 0,
|
|
...options
|
|
};
|
|
|
|
const results = await this.index.search(query, searchOptions);
|
|
return results;
|
|
} catch (error) {
|
|
console.error('Search error:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete a service from the index
|
|
* @param {number} serviceId - Service ID
|
|
*/
|
|
static async deleteService(serviceId) {
|
|
try {
|
|
if (!this.index) {
|
|
await this.init();
|
|
}
|
|
|
|
await this.index.deleteDocument(serviceId);
|
|
console.log(`Deleted service ${serviceId} from index`);
|
|
} catch (error) {
|
|
console.error(`Failed to delete service ${serviceId}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get index stats
|
|
*/
|
|
static async getStats() {
|
|
try {
|
|
if (!this.index) {
|
|
await this.init();
|
|
}
|
|
|
|
const stats = await this.index.getStats();
|
|
return stats;
|
|
} catch (error) {
|
|
console.error('Failed to get stats:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|